├── .watchmanconfig ├── todo.org ├── api ├── resources │ ├── seeds │ │ ├── messages.edn │ │ ├── posts.edn │ │ ├── db.clj │ │ └── topics.edn │ ├── lang │ │ ├── en │ │ │ ├── errors.edn │ │ │ └── validation.edn │ │ └── zh_cn │ │ │ ├── validation.edn │ │ │ └── errors.edn │ ├── log4j.properties │ └── sql │ │ └── init.sql ├── doc │ └── intro.md ├── .gitignore ├── test │ └── api │ │ └── core_test.clj ├── schema.org ├── README.md ├── scripts │ └── facebook_locales.clj ├── src │ └── api │ │ ├── tasks.clj │ │ ├── scripts │ │ ├── scrape.clj │ │ ├── aws.clj │ │ └── channel.clj │ │ ├── handler │ │ ├── report.clj │ │ ├── admin │ │ │ └── auth.clj │ │ ├── auth.clj │ │ ├── util.clj │ │ ├── channel.clj │ │ ├── me.clj │ │ ├── admin.clj │ │ └── invite.clj │ │ ├── repl.clj │ │ ├── flake │ │ ├── timer.clj │ │ └── utils.clj │ │ ├── pg │ │ ├── geometric │ │ │ └── Point.clj │ │ ├── pgpass.clj │ │ ├── coerce.clj │ │ ├── spatial.clj │ │ └── geojson.clj │ │ ├── db │ │ ├── report.clj │ │ ├── ws.clj │ │ ├── mention.clj │ │ ├── invite.clj │ │ ├── stats.clj │ │ ├── notification.clj │ │ └── message.clj │ │ ├── services │ │ ├── s3.clj │ │ ├── token.clj │ │ ├── token │ │ │ └── crypt.clj │ │ ├── twilio.clj │ │ ├── exponent.clj │ │ ├── onesignal.clj │ │ └── slack.clj │ │ ├── schema │ │ ├── util.clj │ │ └── coerce.clj │ │ ├── fnhouse │ │ └── swagger.clj │ │ └── commands │ │ └── commands.clj ├── circle.yml ├── CHANGELOG.md ├── dev │ └── env.clj ├── project.clj └── swagger-ui │ └── index.html ├── src ├── cljsjs │ ├── react.cljs │ └── react │ │ ├── dom.cljs │ │ └── dom │ │ └── server.cljs ├── reagent │ ├── dom │ │ └── server.cljs │ └── dom.cljs └── lymchat │ ├── config.cljs │ ├── ios │ └── core.cljs │ ├── fs.cljs │ ├── shared │ ├── component │ │ └── language.cljs │ └── scene │ │ ├── member.cljs │ │ ├── login.cljs │ │ └── contact.cljs │ ├── locales.cljs │ ├── notification.cljs │ └── db.cljs ├── android ├── app │ ├── google-services.json.example │ ├── src │ │ └── main │ │ │ ├── assets │ │ │ └── fonts │ │ │ │ ├── Entypo.ttf │ │ │ │ ├── Zocial.ttf │ │ │ │ ├── EvilIcons.ttf │ │ │ │ ├── Ionicons.ttf │ │ │ │ ├── Octicons.ttf │ │ │ │ ├── FontAwesome.ttf │ │ │ │ ├── Foundation.ttf │ │ │ │ └── MaterialIcons.ttf │ │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── raw │ │ │ │ └── incallmanager_ringback.mp3 │ │ │ └── values │ │ │ │ ├── styles.xml │ │ │ │ └── strings.xml │ │ │ └── java │ │ │ └── com │ │ │ └── lymchat │ │ │ └── lymchat │ │ │ ├── wxapi │ │ │ └── WXEntryActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── LymReactPackage.java │ │ │ ├── Config.java.example │ │ │ └── MainApplication.java.example │ ├── my-release-key.keystore.example │ ├── BUCK │ └── proguard-rules.pro ├── keystores │ ├── debug.keystore.properties │ └── BUCK ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── build.gradle ├── gradle.properties.example ├── gradlew.bat └── settings.gradle ├── images ├── lym.png ├── logo.png ├── default.png └── intro │ ├── s1.png │ ├── s2.png │ └── s3.png ├── ios ├── incallmanager_ringback.mp3 ├── RNGoogleSignin │ ├── GoogleSignIn.bundle │ │ ├── Info.plist │ │ ├── google.png │ │ ├── gplus.png │ │ ├── google@2x.png │ │ ├── google@3x.png │ │ ├── gplus@2x.png │ │ ├── gplus@3x.png │ │ ├── Roboto-Bold.ttf │ │ ├── ar.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── ca.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── cs.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── da.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── de.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── el.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── en.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── es.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── fi.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── fr.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── he.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── hr.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── hu.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── id.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── it.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── ja.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── ko.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── ms.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── nb.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── nl.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── pl.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── pt.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── ro.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── ru.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── sk.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── sv.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── th.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── tr.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── uk.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── vi.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── en_GB.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── es_MX.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── pt_BR.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── pt_PT.lproj │ │ │ └── GoogleSignIn.strings │ │ ├── zh_CN.lproj │ │ │ └── GoogleSignIn.strings │ │ └── zh_TW.lproj │ │ │ └── GoogleSignIn.strings │ ├── GoogleSignIn.framework │ │ ├── GoogleSignIn │ │ ├── embedded.mobileprovision │ │ ├── _CodeSignature │ │ │ ├── CodeDirectory │ │ │ ├── CodeSignature │ │ │ └── CodeRequirements │ │ └── Headers │ │ │ ├── GoogleSignIn.h │ │ │ ├── GIDProfileData.h │ │ │ ├── GIDGoogleUser.h │ │ │ ├── GIDSignInButton.h │ │ │ └── GIDAuthentication.h │ ├── RNGoogleSigninButtonManager.m │ └── RNGoogleSignin.h ├── Lymchat │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Icon.png │ │ │ ├── Icon@2x.png │ │ │ ├── Icon-60@2x.png │ │ │ ├── Icon-60@3x.png │ │ │ ├── Icon-Small.png │ │ │ ├── Icon-Small@2x.png │ │ │ ├── Icon-Small-40@2x.png │ │ │ ├── Icon-Small-40@3x.png │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ ├── Default@2x.png │ │ │ ├── Default-568h@2x.png │ │ │ ├── Default-667h@2x.png │ │ │ ├── Default-Portrait-736h@3x.png │ │ │ └── Contents.json │ ├── StatusBarBackground.h │ ├── Config.m │ ├── AppDelegate.h │ ├── main.m │ ├── StatusBarBackground.m │ ├── Info.plist │ └── Base.lproj │ │ └── LaunchScreen.xib ├── Lymchat.xcworkspace │ └── contents.xcworkspacedata ├── Podfile ├── RNInCallManager │ ├── RNInCallManager-Bridging-Header.h │ └── RNInCallManagerBridge.m ├── LymchatTests │ ├── Info.plist │ └── LymchatTests.m ├── GoogleService-Info.plist.example └── Podfile.lock ├── env ├── prod │ └── env │ │ ├── ios │ │ └── main.cljs │ │ └── android │ │ └── main.cljs └── dev │ ├── env │ ├── ios │ │ └── main.cljs │ └── android │ │ └── main.cljs │ └── user.clj ├── doc └── intro.md ├── .buckconfig ├── .hgignore ├── test └── lymchat │ └── core_test.clj ├── CHANGELOG.md ├── .gitignore ├── readme.org ├── package.json ├── .re-natal └── .flowconfig /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /todo.org: -------------------------------------------------------------------------------- 1 | * Lymchat TODO 2 | -------------------------------------------------------------------------------- /api/resources/seeds/messages.edn: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/cljsjs/react.cljs: -------------------------------------------------------------------------------- 1 | (ns cljsjs.react) -------------------------------------------------------------------------------- /src/cljsjs/react/dom.cljs: -------------------------------------------------------------------------------- 1 | (ns cljsjs.react.dom) -------------------------------------------------------------------------------- /src/cljsjs/react/dom/server.cljs: -------------------------------------------------------------------------------- 1 | (ns cljsjs.react.dom.server) -------------------------------------------------------------------------------- /android/app/google-services.json.example: -------------------------------------------------------------------------------- 1 | download from google 2 | -------------------------------------------------------------------------------- /images/lym.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/images/lym.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/images/logo.png -------------------------------------------------------------------------------- /images/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/images/default.png -------------------------------------------------------------------------------- /images/intro/s1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/images/intro/s1.png -------------------------------------------------------------------------------- /images/intro/s2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/images/intro/s2.png -------------------------------------------------------------------------------- /images/intro/s3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/images/intro/s3.png -------------------------------------------------------------------------------- /ios/incallmanager_ringback.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/incallmanager_ringback.mp3 -------------------------------------------------------------------------------- /env/prod/env/ios/main.cljs: -------------------------------------------------------------------------------- 1 | (ns env.ios.main 2 | (:require [lymchat.ios.core :as core])) 3 | 4 | (core/init) 5 | 6 | 7 | -------------------------------------------------------------------------------- /api/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to api 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to lymchat 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /env/prod/env/android/main.cljs: -------------------------------------------------------------------------------- 1 | (ns env.android.main 2 | (:require [lymchat.android.core :as core])) 3 | 4 | (core/init) 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/android/app/src/main/assets/fonts/Entypo.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Zocial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/android/app/src/main/assets/fonts/Zocial.ttf -------------------------------------------------------------------------------- /api/resources/lang/en/errors.edn: -------------------------------------------------------------------------------- 1 | {:email-or-password-not-correct "Email or password is not correct." 2 | :login-failed "Login is fail."} 3 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/EvilIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/android/app/src/main/assets/fonts/EvilIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/android/app/src/main/assets/fonts/Ionicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Octicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/android/app/src/main/assets/fonts/Octicons.ttf -------------------------------------------------------------------------------- /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/android/app/src/main/assets/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Foundation.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/android/app/src/main/assets/fonts/Foundation.ttf -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/Info.plist -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/google.png -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/gplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/gplus.png -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/MaterialIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/android/app/src/main/assets/fonts/MaterialIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/google@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/google@2x.png -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/google@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/google@3x.png -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/gplus@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/gplus@2x.png -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/gplus@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/gplus@3x.png -------------------------------------------------------------------------------- /android/app/my-release-key.keystore.example: -------------------------------------------------------------------------------- 1 | keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000 2 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/Roboto-Bold.ttf -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.framework/GoogleSignIn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.framework/GoogleSignIn -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/raw/incallmanager_ringback.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/android/app/src/main/res/raw/incallmanager_ringback.mp3 -------------------------------------------------------------------------------- /android/keystores/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /api/resources/lang/zh_cn/validation.edn: -------------------------------------------------------------------------------- 1 | {:password "密码至少6个字符." 2 | :email "邮箱格式不正确." 3 | :identity "用户ID不正确." 4 | :phone "手机格式不正确." 5 | :uid "email或手机格式不正确"} 6 | -------------------------------------------------------------------------------- /ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /ios/Lymchat/StatusBarBackground.h: -------------------------------------------------------------------------------- 1 | #import "RCTBridgeModule.h" 2 | #import 3 | 4 | @interface StatusBarBackground : NSObject 5 | @end 6 | -------------------------------------------------------------------------------- /ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png -------------------------------------------------------------------------------- /ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/Lymchat/Images.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png -------------------------------------------------------------------------------- /ios/Lymchat/Images.xcassets/LaunchImage.launchimage/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/Lymchat/Images.xcassets/LaunchImage.launchimage/Default@2x.png -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.framework/embedded.mobileprovision: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.framework/embedded.mobileprovision -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | target/** 3 | classes/** 4 | checkouts/** 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | .gitignore 12 | .git/** 13 | -------------------------------------------------------------------------------- /android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = 'debug', 3 | store = 'debug.keystore', 4 | properties = 'debug.keystore.properties', 5 | visibility = [ 6 | 'PUBLIC', 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/ar.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/ar.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/ca.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/ca.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/cs.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/cs.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/da.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/da.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/de.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/de.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/el.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/el.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/en.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/en.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/es.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/es.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/fi.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/fi.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/fr.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/fr.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/he.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/he.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/hr.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/hr.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/hu.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/hu.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/id.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/id.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/it.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/it.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/ja.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/ja.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/ko.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/ko.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/ms.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/ms.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/nb.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/nb.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/nl.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/nl.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/pl.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/pl.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/pt.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/pt.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/ro.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/ro.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/ru.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/ru.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/sk.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/sk.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/sv.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/sv.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/th.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/th.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/tr.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/tr.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/uk.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/uk.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/vi.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/vi.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/Lymchat/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/Lymchat/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png -------------------------------------------------------------------------------- /ios/Lymchat/Images.xcassets/LaunchImage.launchimage/Default-667h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/Lymchat/Images.xcassets/LaunchImage.launchimage/Default-667h@2x.png -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/en_GB.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/en_GB.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/es_MX.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/es_MX.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/pt_BR.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/pt_BR.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/pt_PT.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/pt_PT.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/zh_CN.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/zh_CN.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.bundle/zh_TW.lproj/GoogleSignIn.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.bundle/zh_TW.lproj/GoogleSignIn.strings -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.framework/_CodeSignature/CodeDirectory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.framework/_CodeSignature/CodeDirectory -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.framework/_CodeSignature/CodeSignature: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.framework/_CodeSignature/CodeSignature -------------------------------------------------------------------------------- /api/resources/lang/en/validation.edn: -------------------------------------------------------------------------------- 1 | {:password "Use at least 6 characters. Don’t use a password from another site, or something too obvious like your birthdate." 2 | :email "Email is not legal."} 3 | -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.framework/_CodeSignature/CodeRequirements: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/RNGoogleSignin/GoogleSignIn.framework/_CodeSignature/CodeRequirements -------------------------------------------------------------------------------- /api/test/api/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns api.core-test 2 | (:require [clojure.test :refer :all] 3 | [api.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /src/reagent/dom/server.cljs: -------------------------------------------------------------------------------- 1 | (ns reagent.dom.server) 2 | ;; Shimmed namespace to make reagent 0.6.0 work with react native packager 3 | 4 | (defn render-to-string [_]) 5 | 6 | (defn render-to-static-markup [_]) -------------------------------------------------------------------------------- /ios/Lymchat/Images.xcassets/LaunchImage.launchimage/Default-Portrait-736h@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat/HEAD/ios/Lymchat/Images.xcassets/LaunchImage.launchimage/Default-Portrait-736h@3x.png -------------------------------------------------------------------------------- /test/lymchat/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns lymchat.core-test 2 | (:require [clojure.test :refer :all] 3 | [lymchat.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /api/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=INFO, A1 2 | 3 | log4j.appender.A1=org.apache.log4j.ConsoleAppender 4 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.A1.layout.ConversionPattern= %-5p %c - %m%n 6 | -------------------------------------------------------------------------------- /api/schema.org: -------------------------------------------------------------------------------- 1 | * Schema 2 | ** LocalStorage 3 | *** Conversations 4 | 1. user information 5 | 2. conversations:tienson 6 | 7 | type: zset 8 | score: created_at 9 | data: [{:another :last_message :last_message_at, created_at}] 10 | 3. contacts 11 | -------------------------------------------------------------------------------- /src/reagent/dom.cljs: -------------------------------------------------------------------------------- 1 | (ns reagent.dom) 2 | ;; Shimmed namespace to make reagent 0.6.0 work with react native packager 3 | 4 | (defn render 5 | ([_ _]) 6 | ([_ _ _])) 7 | (defn unmount-component-at-node [_]) 8 | 9 | (defn dom-node [_]) 10 | 11 | (defn force-update-all []) -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Aug 31 11:49:51 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /api/README.md: -------------------------------------------------------------------------------- 1 | # api 2 | 3 | A Clojure library designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2016 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | -------------------------------------------------------------------------------- /src/lymchat/config.cljs: -------------------------------------------------------------------------------- 1 | (ns lymchat.config 2 | (:require [lymchat.util :refer [development?]])) 3 | 4 | ;; add stage server, maybe ngrok? 5 | (defn api-host 6 | [] 7 | (if (development?) 8 | "http://192.168.1.114:8089" 9 | "https://api.lymchat.com")) 10 | 11 | (defonce xxxxx (atom nil)) 12 | -------------------------------------------------------------------------------- /ios/Lymchat.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.framework/Headers/GoogleSignIn.h: -------------------------------------------------------------------------------- 1 | // 2 | // GoogleSignIn.h 3 | // 4 | // Copyright 2016 Google Inc. 5 | // 6 | 7 | #ifndef GOOGLESIGNIN_H 8 | #define GOOGLESIGNIN_H 9 | 10 | #import "GIDAuthentication.h" 11 | #import "GIDGoogleUser.h" 12 | #import "GIDProfileData.h" 13 | #import "GIDSignIn.h" 14 | #import "GIDSignInButton.h" 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'Lymchat' do 5 | # Uncomment this line if you're using Swift or would like to use dynamic frameworks 6 | # use_frameworks! 7 | 8 | # Pods for Lymchat 9 | pod 'Google/SignIn' 10 | target 'LymchatTests' do 11 | inherit! :search_paths 12 | # Pods for testing 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ios/Lymchat/Config.m: -------------------------------------------------------------------------------- 1 | #import "Config.h" 2 | 3 | @implementation Config 4 | RCT_EXPORT_MODULE(); 5 | 6 | - (NSDictionary *)constantsToExport 7 | { 8 | return @{ 9 | @"app_key": APP_KEY, 10 | @"app_secret": APP_SECRET, 11 | @"google_signin_client_id": GOOGLE_SIGN_CLIENT_ID, 12 | @"wechat_id": WECHAT_ID, 13 | @"wechat_secret": WECHAT_SECRET, 14 | @"wechat_state": WECHAT_STATE 15 | }; 16 | } 17 | @end 18 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 207571656306926 22 | fb207571656306926 23 | 24 | 25 | 26 | 27 | 28 | Lymchat 29 | 30 | -------------------------------------------------------------------------------- /ios/RNInCallManager/RNInCallManager-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // RNInCallManager-Bridging-Header.h 2 | // RNInCallManager 3 | // 4 | // Created by zxcpoiu, Henry Hung-Hsien Lin on 2016-04-10 5 | // Copyright 2016 Facebook. All rights reserved. 6 | // 7 | 8 | #ifndef RNInCallManager_Bridging_Header_h 9 | #define RNInCallManager_Bridging_Header_h 10 | #import "RCTBridge.h" 11 | #import "RCTBridgeModule.h" 12 | #import "RCTEventDispatcher.h" 13 | #endif /* RNInCallManager_Bridging_Header_h */ 14 | -------------------------------------------------------------------------------- /api/scripts/facebook_locales.clj: -------------------------------------------------------------------------------- 1 | (ns facebook-locales 2 | (:require [clojure.xml :as xml])) 3 | 4 | (defn get-locale-lang 5 | [c] 6 | (let [c (:content c)] 7 | [(keyword (first (:content (second (:content (first (:content (first (:content (second c)))))))))) 8 | (first (:content (first c)))])) 9 | 10 | (def locales 11 | (->> (xml/parse "https://www.facebook.com/translations/FacebookLocales.xml") 12 | :content 13 | (map get-locale-lang) 14 | (into {}))) 15 | -------------------------------------------------------------------------------- /src/lymchat/ios/core.cljs: -------------------------------------------------------------------------------- 1 | (ns lymchat.ios.core 2 | (:require [reagent.core :as r] 3 | [re-frame.core :refer [dispatch-sync]] 4 | [lymchat.handlers] 5 | [lymchat.subs] 6 | [lymchat.shared.scene.root :as root] 7 | [lymchat.shared.ui :as ui])) 8 | 9 | (def app-root #'root/app-root) 10 | 11 | (defn init [] 12 | (dispatch-sync [:initialize-db]) 13 | (.registerComponent ui/app-registry "Lymchat" #(r/reactify-component app-root))) 14 | -------------------------------------------------------------------------------- /env/dev/env/ios/main.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:figwheel-no-load env.ios.main 2 | (:require [reagent.core :as r] 3 | [lymchat.ios.core :as core] 4 | [figwheel.client :as figwheel :include-macros true])) 5 | 6 | (enable-console-print!) 7 | 8 | (def cnt (r/atom 0)) 9 | (defn reloader [] @cnt [core/app-root]) 10 | (def root-el (r/as-element [reloader])) 11 | 12 | (figwheel/watch-and-reload 13 | :websocket-url "ws://192.168.1.114:3449/figwheel-ws" 14 | :heads-up-display false 15 | :jsload-callback #(swap! cnt inc)) 16 | 17 | (core/init) -------------------------------------------------------------------------------- /env/dev/env/android/main.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:figwheel-no-load env.android.main 2 | (:require [reagent.core :as r] 3 | [lymchat.android.core :as core] 4 | [figwheel.client :as figwheel :include-macros true])) 5 | 6 | (enable-console-print!) 7 | 8 | (def cnt (r/atom 0)) 9 | (defn reloader [] @cnt [core/app-root]) 10 | (def root-el (r/as-element [reloader])) 11 | 12 | (figwheel/watch-and-reload 13 | :websocket-url "ws://localhost:3449/figwheel-ws" 14 | :heads-up-display false 15 | :jsload-callback #(swap! cnt inc)) 16 | 17 | (core/init) -------------------------------------------------------------------------------- /android/app/src/main/java/com/lymchat/lymchat/wxapi/WXEntryActivity.java: -------------------------------------------------------------------------------- 1 | package com.lymchat.lymchat.wxapi; 2 | 3 | /** 4 | * Created by tienson on 8/31/16. 5 | */ 6 | 7 | import android.app.Activity; 8 | import android.os.Bundle; 9 | import com.theweflex.react.WeChatModule; 10 | 11 | public class WXEntryActivity extends Activity{ 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | WeChatModule.handleIntent(getIntent()); 16 | finish(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ios/Lymchat/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (nonatomic, strong) UIWindow *window; 15 | @property (nonatomic, weak) UIView *barView; 16 | @end 17 | -------------------------------------------------------------------------------- /ios/Lymchat/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import "AppDelegate.h" 13 | 14 | int main(int argc, char * argv[]) { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /api/src/api/tasks.clj: -------------------------------------------------------------------------------- 1 | (ns api.tasks 2 | (:require [api.flake.core :as flake] 3 | [api.services.twilio :as twilio] 4 | [api.db.stats :as stats] 5 | [api.db.channel :as channel] 6 | [clojure.java.jdbc :as j] 7 | [api.db.util :as util])) 8 | 9 | (defn run 10 | [] 11 | (prn "Flake Id initial!") 12 | (flake/init!) 13 | (prn "Load channels to memory!") 14 | (j/with-db-connection [db util/default-db] 15 | (channel/load-in-memory db)) 16 | (prn "Periodically update Twilio ice servers.") 17 | (twilio/run) 18 | (prn "Periodically sync stats to db.") 19 | (stats/run)) 20 | -------------------------------------------------------------------------------- /api/circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | timezone: 3 | Asia/Shanghai 4 | java: 5 | version: oraclejdk8 6 | services: 7 | - elasticsearch 8 | environment: 9 | ENVIRONMENT: ci 10 | 11 | database: 12 | override: 13 | - psql -U ubuntu circle_test < migration/resources/migrations/0000-initial-schema.up.sql.sample 14 | - psql -U ubuntu circle_test < resources/sql/init/categories.sql 15 | - cd migration && lein migrate 16 | 17 | # deployment: 18 | # production: 19 | # branch: master 20 | # commands: 21 | # - fab production deploy 22 | 23 | # staging: 24 | # branch: master 25 | # commands: 26 | # - fab stage deploy 27 | -------------------------------------------------------------------------------- /api/src/api/scripts/scrape.clj: -------------------------------------------------------------------------------- 1 | (ns api.scripts.scrape 2 | (:require [net.cgrand.enlive-html :as html] 3 | [api.scripts.aws :as aws])) 4 | 5 | (defn fetch-url [url] 6 | (html/html-resource (java.net.URL. url))) 7 | 8 | (defn attrs 9 | [node selector ks] 10 | (map (fn [node] 11 | (-> (:attrs node) 12 | (select-keys ks) 13 | (vals) 14 | (first))) 15 | (html/select node selector))) 16 | 17 | (defn contents 18 | [node selector] 19 | (map (fn [node] (-> node 20 | :content 21 | (nth 1) 22 | :content 23 | (nth 1) 24 | :content 25 | first)) 26 | (html/select node selector))) 27 | -------------------------------------------------------------------------------- /env/dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:use [figwheel-sidecar.repl-api :as ra])) 3 | ;; This namespace is loaded automatically by nREPL 4 | 5 | ;; read project.clj to get build configs 6 | (def profiles (->> "project.clj" 7 | slurp 8 | read-string 9 | (drop-while #(not= % :profiles)) 10 | (apply hash-map) 11 | :profiles)) 12 | 13 | (def cljs-builds (get-in profiles [:dev :cljsbuild :builds])) 14 | 15 | (defn start-figwheel 16 | "Start figwheel for one or more builds" 17 | [] 18 | (ra/start-figwheel! 19 | {:build-ids ["ios" "android"] 20 | :all-builds cljs-builds 21 | :figwheel-options {}}) 22 | (ra/cljs-repl)) 23 | 24 | (defn stop-figwheel 25 | "Stops figwheel" 26 | [] 27 | (ra/stop-figwheel!)) 28 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.0-rc1' 9 | classpath 'com.google.gms:google-services:3.0.0' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | mavenLocal() 19 | jcenter() 20 | maven { 21 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 22 | url "$rootDir/../node_modules/react-native/android" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ios/LymchatTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /api/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [Unreleased] 5 | ### Changed 6 | - Add a new arity to `make-widget-async` to provide a different widget shape. 7 | 8 | ## [0.1.1] - 2016-05-27 9 | ### Changed 10 | - Documentation on how to make the widgets. 11 | 12 | ### Removed 13 | - `make-widget-sync` - we're all async, all the time. 14 | 15 | ### Fixed 16 | - Fixed widget maker to keep working when daylight savings switches over. 17 | 18 | ## 0.1.0 - 2016-05-27 19 | ### Added 20 | - Files from the new template. 21 | - Widget maker public API - `make-widget-sync`. 22 | 23 | [Unreleased]: https://github.com/your-name/api/compare/0.1.1...HEAD 24 | [0.1.1]: https://github.com/your-name/api/compare/0.1.0...0.1.1 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [Unreleased] 5 | ### Changed 6 | - Add a new arity to `make-widget-async` to provide a different widget shape. 7 | 8 | ## [0.1.1] - 2016-05-28 9 | ### Changed 10 | - Documentation on how to make the widgets. 11 | 12 | ### Removed 13 | - `make-widget-sync` - we're all async, all the time. 14 | 15 | ### Fixed 16 | - Fixed widget maker to keep working when daylight savings switches over. 17 | 18 | ## 0.1.0 - 2016-05-28 19 | ### Added 20 | - Files from the new template. 21 | - Widget maker public API - `make-widget-sync`. 22 | 23 | [Unreleased]: https://github.com/your-name/lymchat/compare/0.1.1...HEAD 24 | [0.1.1]: https://github.com/your-name/lymchat/compare/0.1.0...0.1.1 25 | -------------------------------------------------------------------------------- /api/src/api/handler/report.clj: -------------------------------------------------------------------------------- 1 | (ns api.handler.report 2 | (:require [api 3 | [schemas :refer :all] 4 | [util :refer [doc]]] 5 | [api.db 6 | [report :as report] 7 | [util :refer [default-db]]] 8 | [api.services.slack :refer [error]] 9 | [environ-plus.core :refer [env]] 10 | [plumbing.core :refer [defnk]] 11 | [schema.core :as s] 12 | [clojure.java.jdbc :as j] 13 | [api.pg.core :as pg])) 14 | 15 | (defnk $POST 16 | "Create new report." 17 | {:responses {201 s/Any 18 | 401 Unauthorized 19 | 400 Wrong 20 | 404 NotFound}} 21 | [[:request body :- NewReport]] 22 | (j/with-db-connection [db default-db] 23 | (let [new-report (report/create db body)] 24 | {:status 201 25 | :body new-report}))) 26 | -------------------------------------------------------------------------------- /api/src/api/repl.clj: -------------------------------------------------------------------------------- 1 | (ns api.repl 2 | "Live debug, copy from riemann." 3 | (:require [clojure.tools.nrepl.server :as nrepl])) 4 | 5 | (defonce server nil) 6 | 7 | (defn stop-server! 8 | "Stops the REPL server." 9 | [] 10 | (when-let [s server] 11 | (nrepl/stop-server s)) 12 | (def server nil)) 13 | 14 | (defn start-server! 15 | "Starts a new repl server. Stops the old server first, if any. Options: 16 | 17 | :host (default \"127.0.0.1\") 18 | :port (default 8988)" 19 | [opts] 20 | (stop-server!) 21 | (let [opts (merge {:port 8991 :host "127.0.0.1"} opts)] 22 | (def server (nrepl/start-server 23 | :port (:port opts) 24 | :bind (:host opts))) 25 | (prn "REPL server" opts "online"))) 26 | 27 | (defn start-server 28 | "Starts a new REPL server, when one isn't already running." 29 | [opts] 30 | (when-not server (start-server! opts))) 31 | -------------------------------------------------------------------------------- /api/src/api/flake/timer.clj: -------------------------------------------------------------------------------- 1 | (ns api.flake.timer 2 | (:require [clojure.java.io :as io] 3 | [api.flake.utils :as utils])) 4 | 5 | (defn write-timestamp 6 | "Periodically writes the most recent timestamp to path." 7 | [path f epoch] 8 | (future 9 | (loop [next-update (+ 1e3 (utils/now))] 10 | (with-open [w (io/writer path)] 11 | (let [now (utils/now-from-epoch epoch) 12 | ts (.ts @f)] 13 | (.write w (str (if (> ts now) ts now))))) 14 | 15 | ;; Sleep for the difference between 1000ms and time spent. 16 | (Thread/sleep (- next-update (utils/now))) 17 | (recur (+ 1e3 next-update))))) 18 | 19 | (defn read-timestamp 20 | "Reads a timestamp from path. If the path is not a file, returns 0." 21 | [path] 22 | (try 23 | (read-string (slurp path)) 24 | (catch java.lang.RuntimeException _ 0) 25 | (catch java.io.IOException _ 0))) 26 | -------------------------------------------------------------------------------- /api/src/api/pg/geometric/Point.clj: -------------------------------------------------------------------------------- 1 | (ns api.pg.geometric.Point 2 | "Example implementation of a PGobject that also implements 3 | Clojure's ISeq and shows as a sequence of two numbers." 4 | (:gen-class 5 | :extends org.postgresql.geometric.PGpoint 6 | :implements [clojure.lang.Counted clojure.lang.ISeq] 7 | :main false)) 8 | 9 | (defn to-list 10 | [^org.postgresql.geometric.PGpoint this] 11 | (list (.-x this) (.-y this))) 12 | 13 | (defn -count 14 | [this] 15 | (.count (to-list this))) 16 | 17 | (defn -first 18 | [this] 19 | (.first (to-list this))) 20 | 21 | (defn -next 22 | [this] 23 | (.next (to-list this))) 24 | 25 | (defn -more 26 | [this] 27 | (.more (to-list this))) 28 | 29 | (defn -empty 30 | [this] 31 | (.empty (to-list this))) 32 | 33 | (defn -equiv 34 | [this other] 35 | (.equiv (to-list this) (to-list other))) 36 | 37 | (defn -seq 38 | [this] 39 | (.seq (to-list this))) 40 | -------------------------------------------------------------------------------- /ios/GoogleService-Info.plist.example: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 7 | REVERSED_CLIENT_ID 8 | 9 | PLIST_VERSION 10 | 1 11 | BUNDLE_ID 12 | com.lymchat.lymchat 13 | PROJECT_ID 14 | 15 | IS_ADS_ENABLED 16 | 17 | IS_ANALYTICS_ENABLED 18 | 19 | IS_APPINVITE_ENABLED 20 | 21 | IS_GCM_ENABLED 22 | 23 | IS_SIGNIN_ENABLED 24 | 25 | GOOGLE_APP_ID 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /api/src/api/db/report.clj: -------------------------------------------------------------------------------- 1 | (ns api.db.report 2 | (:require [clojure.java.jdbc :as j] 3 | [api.db.util :refer [with-now]])) 4 | 5 | (defonce ^:private table "reports") 6 | 7 | (defn create 8 | [db m] 9 | (-> (j/insert! db table (-> m 10 | (with-now [:created_at]))) 11 | first)) 12 | 13 | (defn get-reports 14 | [db limit] 15 | (j/query db ["select r.id, r.user_id, r.type, r.type_id, r.title, r.picture, r.description, r.created_at, r.block, users.name, users.avatar from reports as r 16 | left join users on users.id = r.user_id 17 | order by r.created_at desc 18 | limit ?" limit])) 19 | 20 | (defn block 21 | [db type type-id] 22 | (j/update! db table {:block true} 23 | ["type = ? and type_id = ?" (name type) type-id])) 24 | 25 | (defn unblock 26 | [db type type-id] 27 | (j/update! db table {:block false} 28 | ["type = ? and type_id = ?" (name type) type-id])) 29 | -------------------------------------------------------------------------------- /api/resources/seeds/posts.edn: -------------------------------------------------------------------------------- 1 | [{:user_id #uuid "20000000-81c1-4f1a-aa22-eeb57d2eea98" 2 | :picture "http://static3.businessinsider.com/image/54d11789ecad04b16d8f9527-480/tim-duncan.jpg" 3 | :caption "I'm retired!" 4 | :hashtags ["timduncan" "retire"]} 5 | {:user_id #uuid "20000000-81c1-4f1a-aa22-eeb57d2eea98" 6 | :picture "http://images.performgroup.com/di/library/sporting_news/87/1c/manu-ginobili_nqbelamkt3f719ywkkj5h4v40.jpg?t=1199571342&h=600" 7 | :caption "Good!" 8 | :hashtags ["good" "manuginobili"]} 9 | {:user_id #uuid "6c0c4d91-7ed2-4fd3-af63-7ef2d394fd81" 10 | :picture "http://images.performgroup.com/di/library/sporting_news/87/1c/manu-ginobili_nqbelamkt3f719ywkkj5h4v40.jpg?t=1199571342&h=600" 11 | :caption "Good" 12 | :hashtags []} 13 | {:user_id #uuid "6c0c4d91-7ed2-4fd3-af63-7ef2d394fd81" 14 | :picture "http://images.performgroup.com/di/library/sporting_news/87/1c/manu-ginobili_nqbelamkt3f719ywkkj5h4v40.jpg?t=1199571342&h=600" 15 | :caption "Nice" 16 | :hashtags []}] 17 | -------------------------------------------------------------------------------- /api/src/api/handler/admin/auth.clj: -------------------------------------------------------------------------------- 1 | (ns api.handler.admin.auth 2 | (:require [api 3 | [schemas :refer :all] 4 | [util :refer [app-auth? doc production?]]] 5 | [api.db 6 | [user :as user] 7 | [util :refer [default-db]]] 8 | [api.services.slack :refer [error]] 9 | [api.handler.util :refer [admin-token]] 10 | [environ-plus.core :refer [env]] 11 | [plumbing.core :refer [defnk]] 12 | [schema.core :as s] 13 | [api.pg.core :as pg] 14 | [clojure.java.jdbc :as j])) 15 | 16 | (defnk admin$POST 17 | "admin login." 18 | {:responses {200 s/Any 19 | 401 Unauthorized 20 | 400 Wrong}} 21 | [[:request 22 | [:body key :- s/Str secret :- s/Str]]] 23 | (j/with-db-connection [db default-db] 24 | (if-not (contains? (:admin env) {:key key 25 | :secret secret}) 26 | (unauthorized) 27 | {:body (admin-token db)}))) 28 | -------------------------------------------------------------------------------- /api/src/api/scripts/aws.clj: -------------------------------------------------------------------------------- 1 | (ns api.scripts.aws 2 | (:use [amazonica.aws.s3] 3 | [amazonica.aws.s3transfer]) 4 | (:require [clojure.java.io :as io] 5 | [amazonica.core :as core] 6 | [environ-plus.core :refer [env]] 7 | [api.util :refer [jpeg? png?]]) 8 | (:import [java.nio.file Files] 9 | [java.nio.file Path])) 10 | 11 | (defn copy-uri-to-file [uri file] 12 | (with-open [in (clojure.java.io/input-stream uri) 13 | out (clojure.java.io/output-stream file)] 14 | (clojure.java.io/copy in out))) 15 | 16 | (defn put-image 17 | [name uri] 18 | (copy-uri-to-file uri "/tmp/test.jpg") 19 | (let [{:keys [accessKey secretKey region]} (:s3-options (:xxxxx env))] 20 | (core/with-credential [accessKey secretKey region] 21 | (put-object :bucket-name "lymchat" 22 | :key (format "pics/%s.jpg" name) 23 | :file (io/file "/tmp/test.jpg"))) 24 | (str "http://d24ujvixi34248.cloudfront.net/" (format "pics/%s.jpg" name)))) 25 | -------------------------------------------------------------------------------- /api/resources/seeds/db.clj: -------------------------------------------------------------------------------- 1 | (ns seeds.db 2 | (:require [api.pg.core :refer [point]] 3 | [api.db.user :as user] 4 | [api.db.util :refer [default-db]] 5 | [api.util :refer [flake-id]] 6 | [api.pg.core :as pg] 7 | [clojure.java.jdbc :as j])) 8 | 9 | (def seeds 10 | {:users (read-string (slurp "resources/seeds/users.edn")) 11 | :channels (read-string (slurp "resources/seeds/channels.edn")) 12 | }) 13 | 14 | (defn truncate! 15 | [] 16 | (j/execute! default-db ["truncate table users; 17 | truncate table invites;"])) 18 | 19 | (defn import! 20 | [] 21 | (let [{:keys [users]} seeds] 22 | (prn "---------- Import users ----------") 23 | (dorun (map #(user/create default-db (assoc % :flake_id (flake-id))) users)))) 24 | 25 | (defn init! 26 | [] 27 | (prn "---------- Truncate table users, topics ----------") 28 | (truncate!) 29 | 30 | (import!) 31 | 32 | (prn "---------- Import is over! ----------")) 33 | -------------------------------------------------------------------------------- /api/src/api/db/ws.clj: -------------------------------------------------------------------------------- 1 | (ns api.db.ws 2 | (:require [clojure.java.jdbc :as j] 3 | [api.db.user :as user] 4 | [api.db.channel :as channel] 5 | [api.db.message :as message] 6 | [api.db.invite :as invite] 7 | [environ-plus.core :refer [env]] 8 | [taoensso.timbre :as t])) 9 | 10 | (defn string->uuid 11 | [s] 12 | (when (string? s) 13 | (java.util.UUID/fromString s))) 14 | 15 | (defn sync-all 16 | [db user-id contacts-ids] 17 | (try 18 | (let [user-id (string->uuid user-id) 19 | contacts-ids (doall (map string->uuid contacts-ids))] 20 | (j/with-db-connection [conn db] 21 | (let [channels-ids (:channels (user/get conn user-id))] 22 | {:new-contacts (user/diff-contacts conn user-id contacts-ids) 23 | :new-invites (invite/get-my-invites conn user-id) 24 | :channels (channel/get-channels-members-count conn channels-ids) 25 | :channels-messages (message/batch-get-channels-latest-messages (:redis env) channels-ids 10)}))) 26 | (catch Exception e 27 | (t/error e)))) 28 | -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.framework/Headers/GIDProfileData.h: -------------------------------------------------------------------------------- 1 | /* 2 | * GIDProfileData.h 3 | * Google Sign-In iOS SDK 4 | * 5 | * Copyright 2014 Google Inc. 6 | * 7 | * Use of this SDK is subject to the Google APIs Terms of Service: 8 | * https://developers.google.com/terms/ 9 | */ 10 | 11 | #import 12 | 13 | // This class represents the basic profile information of a GIDGoogleUser. 14 | @interface GIDProfileData : NSObject 15 | 16 | // The Google user's email. 17 | @property(nonatomic, readonly) NSString *email; 18 | 19 | // The Google user's full name. 20 | @property(nonatomic, readonly) NSString *name; 21 | 22 | // The Google user's given name. 23 | @property(nonatomic, readonly) NSString *givenName; 24 | 25 | // The Google user's family name. 26 | @property(nonatomic, readonly) NSString *familyName; 27 | 28 | // Whether or not the user has profile image. 29 | @property(nonatomic, readonly) BOOL hasImage; 30 | 31 | // Gets the user's profile image URL for the given dimension in pixels for each side of the square. 32 | - (NSURL *)imageURLWithDimension:(NSUInteger)dimension; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /android/gradle.properties.example: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.useDeprecatedNdk=true 21 | MYAPP_RELEASE_STORE_FILE=my-release-key.keystore 22 | MYAPP_RELEASE_KEY_ALIAS=my-key-alias 23 | MYAPP_RELEASE_STORE_PASSWORD= 24 | MYAPP_RELEASE_KEY_PASSWORD= 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IJ 26 | # 27 | *.iml 28 | .idea 29 | .gradle 30 | local.properties 31 | 32 | # node.js 33 | # 34 | node_modules/ 35 | npm-debug.log 36 | 37 | # BUCK 38 | buck-out/ 39 | \.buckd/ 40 | android/app/libs 41 | android/keystores/debug.keystore 42 | 43 | ## Additional for IntelliJ 44 | target/ 45 | figwheel_server.log 46 | 47 | # pods 48 | ios/Pods/ 49 | 50 | android/gradle/wrapper/gradle-wrapper.jar 51 | 52 | ## configuration 53 | ios/Lymchat/config.h 54 | ios/GoogleService-Info.plist 55 | ios/Lymchat.xcodeproj/project.pbxproj 56 | android/gradle.properties 57 | android/app/google-services.json 58 | android/app/my-release-key.keystore 59 | android/app/src/main/java/com/lymchat/lymchat/config.java 60 | android/app/src/main/java/com/lymchat/lymchat/MainApplication.java 61 | .nrepl-port 62 | -------------------------------------------------------------------------------- /api/src/api/services/s3.clj: -------------------------------------------------------------------------------- 1 | (ns api.services.s3 2 | (:use [amazonica.aws.s3] 3 | [amazonica.aws.s3transfer]) 4 | (:require [clojure.java.io :as io] 5 | [amazonica.core :as core] 6 | [environ-plus.core :refer [env]] 7 | [api.util :refer [development?]] 8 | [byte-streams :as bs] 9 | [taoensso.timbre :as t])) 10 | 11 | (defn copy-uri-to-file [uri file] 12 | (with-open [in (clojure.java.io/input-stream uri) 13 | out (clojure.java.io/output-stream file)] 14 | (clojure.java.io/copy in out))) 15 | 16 | (defn put-image 17 | [name uri] 18 | (try 19 | (let [tmp-path (str "/tmp/" name ".jpg") 20 | name (if (development?) (str "developments/" name) name) 21 | {:keys [accessKey secretKey region]} (:s3-options (:xxxxx env))] 22 | (copy-uri-to-file uri tmp-path) 23 | (let [file (io/file tmp-path)] 24 | (core/with-credential [accessKey secretKey region] 25 | (put-object :bucket-name "lymchat" 26 | :key (format "pics/%s.jpg" name) 27 | :file file)))) 28 | (catch Exception e 29 | (t/error e)))) 30 | -------------------------------------------------------------------------------- /readme.org: -------------------------------------------------------------------------------- 1 | * Lymchat 2 | 3 | *** Why? 4 | **** 1. A lot cultures are interesting. 5 | **** 2. Improve your understanding of the world. 6 | **** 3. You don’t want just take a quick tour. 7 | 8 | 9 | *** Lymchat is for 10 | **** 1. Students, you can easily find native speakers, video chat to improve your pronunciation. 11 | **** 2. Travelers, backpackers, you can join different countries, cities groups. 12 | **** 3. People who want to find similar interests friends. 13 | 14 | *** You can 15 | **** 1. Talk to people with same interests. 16 | **** 2. 1-on-1 Unlimited video chat, you can also send text, photos. 17 | **** 3. Join different groups, Food, Photography, Architecture, Art, Tea, Book, etc… 18 | 19 | *** Completely free 20 | 21 | 22 | ** Lymchat - 学外语,了解不同的文化 23 | 24 | *** Why? 25 | **** 1. 世界上有很多有意思的文化 26 | **** 2. 你不会永远想要走马观花的旅行 27 | **** 3. 更好的理解这个世界 28 | **** 4. 你有一个喜欢却暂时不能去的地方 29 | 30 | *** Lym希望帮助: 31 | **** 1. 学生,尤其留学生,你可以在不同的语言或者国家群组里学习,互助. 32 | **** 2. 驴友,背包客,持续添加旅游目的地群组,向当地人了解详情或者求助。 33 | **** 3. 找相同兴趣的朋友 34 | ***** a. NBA球队 35 | ***** b. 足球豪门 36 | ***** c. 茶,戏曲,手艺,日本插画,动漫,编程,传统文化等 37 | 38 | *** 你可以: 39 | **** 1. 加入不同的群组,和兴趣相投的朋友聊天 40 | **** 2. 一对一视频聊天 41 | -------------------------------------------------------------------------------- /ios/RNGoogleSignin/RNGoogleSigninButtonManager.m: -------------------------------------------------------------------------------- 1 | 2 | #import "RCTViewManager.h" 3 | #import "RNGoogleSignIn.h" 4 | 5 | @interface RNGoogleSigninButtonManager : RCTViewManager 6 | @end 7 | 8 | @implementation RNGoogleSigninButtonManager 9 | 10 | RCT_EXPORT_MODULE() 11 | 12 | - (UIView *)view 13 | { 14 | GIDSignInButton *button = [[GIDSignInButton alloc] init]; 15 | button.colorScheme = kGIDSignInButtonColorSchemeLight; 16 | button.style = kGIDSignInButtonStyleStandard; 17 | 18 | // [button removeTarget:nil action:NULL forControlEvents:UIControlEventTouchUpInside]; 19 | // [button addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside]; 20 | return button; 21 | } 22 | 23 | RCT_CUSTOM_VIEW_PROPERTY(color, NSString, GIDSignInButton) 24 | { 25 | view.colorScheme = json ? [RCTConvert GIDSignInButtonColorScheme:json] :kGIDSignInButtonColorSchemeLight; 26 | 27 | } 28 | 29 | RCT_CUSTOM_VIEW_PROPERTY(size, NSString, GIDSignInButton) 30 | { 31 | view.style = json ? [RCTConvert GIDSignInButtonStyle:json] :kGIDSignInButtonStyleStandard; 32 | 33 | } 34 | 35 | //-(void)buttonAction:(GIDSignInButton*)sender 36 | //{ 37 | // 38 | //} 39 | 40 | @end -------------------------------------------------------------------------------- /android/app/src/main/java/com/lymchat/lymchat/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.lymchat.lymchat; 2 | 3 | import android.content.Intent; 4 | 5 | import com.facebook.react.ReactActivity; 6 | import com.zxcpoiu.incallmanager.InCallManagerPackage; 7 | 8 | public class MainActivity extends ReactActivity { 9 | 10 | /** 11 | * Returns the name of the main component registered from JavaScript. 12 | * This is used to schedule rendering of the component. 13 | */ 14 | @Override 15 | protected String getMainComponentName() { 16 | return "Lymchat"; 17 | } 18 | 19 | @Override 20 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 21 | super.onActivityResult(requestCode, resultCode, data); 22 | MainApplication.getCallbackManager().onActivityResult(requestCode, resultCode, data); 23 | } 24 | 25 | @Override 26 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 27 | InCallManagerPackage.onRequestPermissionsResult(requestCode, permissions, grantResults); 28 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/lymchat/lymchat/LymReactPackage.java: -------------------------------------------------------------------------------- 1 | package com.lymchat.lymchat; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | /** 14 | * Created by tienson on 9/7/16. 15 | */ 16 | public class LymReactPackage implements ReactPackage { 17 | @Override 18 | public List> createJSModules() { 19 | return Collections.emptyList(); 20 | } 21 | 22 | @Override 23 | public List createViewManagers(ReactApplicationContext reactContext) { 24 | return Collections.emptyList(); 25 | } 26 | 27 | @Override 28 | public List createNativeModules( 29 | ReactApplicationContext reactContext) { 30 | List modules = new ArrayList<>(); 31 | 32 | modules.add(new Config(reactContext)); 33 | 34 | return modules; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ios/Lymchat/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "extent": "full-screen", 5 | "idiom": "iphone", 6 | "filename": "Default-568h@2x.png", 7 | "minimum-system-version": "7.0", 8 | "orientation": "portrait", 9 | "scale": "2x", 10 | "subtype": "retina4" 11 | }, 12 | { 13 | "extent": "full-screen", 14 | "idiom": "iphone", 15 | "filename": "Default-667h@2x.png", 16 | "minimum-system-version": "8.0", 17 | "orientation": "portrait", 18 | "scale": "2x", 19 | "subtype": "667h" 20 | }, 21 | { 22 | "extent": "full-screen", 23 | "idiom": "iphone", 24 | "filename": "Default-Portrait-736h@3x.png", 25 | "minimum-system-version": "8.0", 26 | "orientation": "portrait", 27 | "scale": "3x", 28 | "subtype": "736h" 29 | }, 30 | { 31 | "extent": "full-screen", 32 | "idiom": "iphone", 33 | "filename": "Default@2x.png", 34 | "minimum-system-version": "7.0", 35 | "orientation": "portrait", 36 | "scale": "2x" 37 | } 38 | ], 39 | "info": { 40 | "version": 1, 41 | "author": "xcode" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /api/src/api/db/mention.clj: -------------------------------------------------------------------------------- 1 | (ns api.db.mention 2 | (:refer-clojure :exclude [get]) 3 | (:require [taoensso.carmine :as car] 4 | [api.util :refer [wcar*]] 5 | [environ-plus.core :refer [env]]) 6 | (:import [com.twitter Extractor])) 7 | 8 | (defonce ^:private redis-key "mentions:") 9 | (defonce ^:private extractor (Extractor.)) 10 | 11 | (defn- rk 12 | [user-id] 13 | (str redis-key user-id)) 14 | 15 | (defn extract 16 | [body] 17 | (some-> (.extractMentionedScreennames extractor body) 18 | (distinct))) 19 | 20 | (defn create 21 | [db user-id message] 22 | (wcar* db 23 | (car/zadd (rk user-id) 24 | (:id message) 25 | message))) 26 | 27 | (defn get 28 | ([db user-id] 29 | (get db user-id nil)) 30 | ([db user-id {:keys [before-id after-id limit] 31 | :or {limit 20}}] 32 | (let [key (rk user-id)] 33 | (cond 34 | (and (nil? before-id) (nil? after-id)) 35 | (wcar* db (car/zrevrange key 0 (dec limit))) 36 | 37 | before-id 38 | (wcar* db (car/zrevrangebyscore key before-id "-inf" "limit" 0 limit)) 39 | 40 | after-id 41 | (wcar* db (car/zrangebyscore key after-id "+inf")))))) 42 | -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.framework/Headers/GIDGoogleUser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * GIDGoogleUser.h 3 | * Google Sign-In iOS SDK 4 | * 5 | * Copyright 2014 Google Inc. 6 | * 7 | * Use of this SDK is subject to the Google APIs Terms of Service: 8 | * https://developers.google.com/terms/ 9 | */ 10 | 11 | #import 12 | 13 | @class GIDAuthentication; 14 | @class GIDProfileData; 15 | 16 | // This class represents a user account. 17 | @interface GIDGoogleUser : NSObject 18 | 19 | // The Google user ID. 20 | @property(nonatomic, readonly) NSString *userID; 21 | 22 | // Representation of the Basic profile data. It is only available if |shouldFetchBasicProfile| 23 | // is set and either |signInWithUser| or |SignIn| has been completed successfully. 24 | @property(nonatomic, readonly) GIDProfileData *profile; 25 | 26 | // The authentication object for the user. 27 | @property(nonatomic, readonly) GIDAuthentication *authentication; 28 | 29 | // The API scopes requested by the app in an array of |NSString|s. 30 | @property(nonatomic, readonly) NSArray *accessibleScopes; 31 | 32 | // For Google Apps hosted accounts, the domain of the user. 33 | @property(nonatomic, readonly) NSString *hostedDomain; 34 | 35 | // An OAuth2 authorization code for the home server. 36 | @property(nonatomic, readonly) NSString *serverAuthCode; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /api/src/api/services/token.clj: -------------------------------------------------------------------------------- 1 | (ns api.services.token 2 | (require [taoensso.carmine :as car] 3 | [environ-plus.core :refer [env]] 4 | [api.util :refer [uuid wcar*]] 5 | [api.services.token.crypt :as crypt] 6 | [clojure.string :as string])) 7 | 8 | (defonce refresh-tokens "refresh_tokens") 9 | 10 | (defn save-token 11 | [tokens key token] 12 | (wcar* 13 | (:redis env) 14 | (car/hset tokens key token))) 15 | 16 | (defn token-exists? 17 | [tokens key token] 18 | (and token (= token (wcar* 19 | (:redis env) 20 | (car/hget tokens key))))) 21 | 22 | (defn get-token 23 | [tokens key] 24 | (wcar* 25 | (:redis env) 26 | (car/hget tokens key))) 27 | 28 | (defn delete-token 29 | [tokens key] 30 | (wcar* 31 | (:redis env) 32 | (car/hdel tokens key))) 33 | 34 | (defn get-refresh-token 35 | [key secret] 36 | (if-let [refresh-token (wcar* 37 | (:redis env) 38 | (car/hget refresh-tokens key))] 39 | refresh-token 40 | (let [token (crypt/encrypt secret (str key "," (uuid)))] 41 | (save-token refresh-tokens key token) 42 | token))) 43 | 44 | (defn extract-app-key 45 | [secret refresh-token] 46 | (-> secret 47 | (crypt/decrypt refresh-token) 48 | (string/split #",") 49 | first)) 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Lymchat", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node_modules/react-native/packager/packager.sh --nonPersistent" 7 | }, 8 | "dependencies": { 9 | "apsl-react-native-button": "^2.5.0", 10 | "babylon": "^6.8.4", 11 | "moment": "^2.13.0", 12 | "react": "^15.3.0", 13 | "react-native": "^0.32.0", 14 | "react-native-action-button": "^2.0.3", 15 | "react-native-app-intro": "^1.1.3", 16 | "react-native-aws3": "0.0.1", 17 | "react-native-code-push": "^v1.14.6-beta", 18 | "react-native-device-info": "^0.9.4", 19 | "react-native-drawer": "^2.3.0", 20 | "react-native-fbsdk": "0.2.1", 21 | "react-native-fs": "^1.5.1", 22 | "react-native-gifted-chat": "0.0.6", 23 | "react-native-google-signin": "^v0.8.0", 24 | "react-native-image-picker": "^0.20.1", 25 | "react-native-image-progress": "^0.6.0", 26 | "react-native-image-resizer": "0.0.9", 27 | "react-native-linear-gradient": "^1.5.12", 28 | "react-native-material-kit": "^0.3.2", 29 | "react-native-modalbox": "^1.3.4", 30 | "react-native-progress": "^3.0.1", 31 | "react-native-push-notification": "^2.0.2", 32 | "react-native-vector-icons": "^2.0.3", 33 | "react-native-webrtc": "^0.11.0", 34 | "react-native-wechat": "^1.5.3", 35 | "realm": "^0.14.3" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.re-natal: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Lymchat", 3 | "interface": "reagent6", 4 | "androidHost": "localhost", 5 | "iosHost": "192.168.1.114", 6 | "envRoots": { 7 | "dev": "env/dev", 8 | "prod": "env/prod" 9 | }, 10 | "modules": [ 11 | "SwipeableListView", 12 | "SwipeableQuickActions", 13 | "SwipeableQuickActionButton", 14 | "react-native-fbsdk", 15 | "react-native-webrtc", 16 | "apsl-react-native-button", 17 | "react-native-vector-icons/FontAwesome", 18 | "react-native-vector-icons/MaterialIcons", 19 | "react-native-linear-gradient", 20 | "realm", 21 | "realm/react-native", 22 | "moment", 23 | "react-native-image-picker", 24 | "react-native-image-resizer", 25 | "react-native-aws3", 26 | "react-native-push-notification", 27 | "react-native-google-signin", 28 | "react-native-modalbox", 29 | "react-native-code-push", 30 | "react-native-fs", 31 | "react-native-parsed-text", 32 | "react-native-app-intro", 33 | "react-native-incall-manager", 34 | "react-native-gifted-chat", 35 | "react-native-wechat", 36 | "react-native-device-info", 37 | "react-native-image-progress", 38 | "react-native-progress", 39 | "react-native-drawer", 40 | "react-native-action-button", 41 | "react-native-dialogs", 42 | "react-native-material-kit" 43 | ], 44 | "imageDirs": [ 45 | "images" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /ios/Lymchat/StatusBarBackground.m: -------------------------------------------------------------------------------- 1 | #import "StatusBarBackground.h" 2 | 3 | @implementation StatusBarBackground 4 | RCT_EXPORT_MODULE(); 5 | - (dispatch_queue_t)methodQueue 6 | { 7 | return dispatch_get_main_queue(); 8 | } 9 | 10 | 11 | RCT_EXPORT_METHOD(setBackgroundColor:(CGFloat)red :(CGFloat)green :(CGFloat)blue :(CGFloat)alpha) 12 | { 13 | 14 | UIViewController *rootViewController = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; 15 | 16 | UIView *view=[[UIView alloc] initWithFrame:CGRectMake(0, 0,[UIScreen mainScreen].bounds.size.width, 20)]; 17 | view.backgroundColor = [UIColor colorWithRed: red 18 | green: green 19 | blue: blue 20 | alpha: alpha]; 21 | view.tag=27; 22 | [rootViewController.view addSubview:view]; 23 | } 24 | 25 | RCT_EXPORT_METHOD(removeBarView) 26 | { 27 | UIViewController *rootViewController = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; 28 | UIView *viewToRemove = [rootViewController.view viewWithTag:27]; 29 | [viewToRemove removeFromSuperview]; 30 | } 31 | 32 | RCT_EXPORT_METHOD(removeBackgroundColor) 33 | { 34 | UIViewController *rootViewController = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; 35 | rootViewController.view.backgroundColor = [UIColor whiteColor]; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /ios/RNInCallManager/RNInCallManagerBridge.m: -------------------------------------------------------------------------------- 1 | // RNInCallManagerBridge.m 2 | // RNInCallManager 3 | // 4 | // Created by zxcpoiu, Henry Hung-Hsien Lin on 2016-04-10 5 | // Copyright 2016 Facebook. All rights reserved. 6 | // 7 | 8 | #import "RCTBridgeModule.h" 9 | 10 | @interface RCT_EXTERN_REMAP_MODULE(InCallManager, RNInCallManager, NSObject) 11 | 12 | RCT_EXTERN_METHOD(start:(NSString *)mediaType auto:(BOOL)auto ringbackUriType:(NSString *)ringbackUriType) 13 | RCT_EXTERN_METHOD(stop:(NSString *)busytone) 14 | RCT_EXTERN_METHOD(turnScreenOn) 15 | RCT_EXTERN_METHOD(turnScreenOff) 16 | RCT_EXTERN_METHOD(setKeepScreenOn:(BOOL)enable) 17 | RCT_EXTERN_METHOD(setSpeakerphoneOn:(BOOL)enable) 18 | RCT_EXTERN_METHOD(setForceSpeakerphoneOn:(int)flag) 19 | RCT_EXTERN_METHOD(setMicrophoneMute:(BOOL)enable) 20 | RCT_EXTERN_METHOD(stopRingback) 21 | RCT_EXTERN_METHOD(startRingtone:(NSString *)ringtoneUriType ringtoneCategory:(NSString *)ringtoneCategory) 22 | RCT_EXTERN_METHOD(stopRingtone) 23 | RCT_EXTERN_METHOD(checkRecordPermission:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 24 | RCT_EXTERN_METHOD(requestRecordPermission:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 25 | RCT_EXTERN_METHOD(checkCameraPermission:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 26 | RCT_EXTERN_METHOD(requestCameraPermission:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /ios/RNGoogleSignin/RNGoogleSignin.h: -------------------------------------------------------------------------------- 1 | #ifndef RN_GoogleSigning_h 2 | #define RN_GoogleSigning_h 3 | 4 | #import "RCTBridgeModule.h" 5 | #import "RCTConvert.h" 6 | 7 | #import 8 | 9 | @interface RNGoogleSignin : NSObject 10 | 11 | + (BOOL)application:(UIApplication *)application openURL:(NSURL *)url 12 | sourceApplication:(NSString *)sourceApplication annotation:(id)annotation; 13 | 14 | @end 15 | 16 | @implementation RCTConvert(GIDSignInButtonStyle) 17 | 18 | RCT_ENUM_CONVERTER(GIDSignInButtonStyle, (@{ 19 | @"standard": @(kGIDSignInButtonStyleStandard), 20 | @"wide": @(kGIDSignInButtonStyleWide), 21 | @"icon": @(kGIDSignInButtonStyleIconOnly), 22 | }), kGIDSignInButtonStyleStandard, integerValue) 23 | 24 | 25 | @end 26 | 27 | @implementation RCTConvert(GIDSignInButtonColorScheme) 28 | 29 | RCT_ENUM_CONVERTER(GIDSignInButtonColorScheme, (@{ 30 | @"dark": @(kGIDSignInButtonColorSchemeDark), 31 | @"light": @(kGIDSignInButtonColorSchemeLight), 32 | }), kGIDSignInButtonColorSchemeDark, integerValue) 33 | 34 | @end 35 | 36 | 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /api/resources/lang/zh_cn/errors.edn: -------------------------------------------------------------------------------- 1 | {:email-or-password-not-correct "用户名或密码不正确." 2 | :uid-or-password-not-correct "用户名或密码不正确." 3 | :login-failed "登录失败." 4 | :duplicate-email "邮箱已经被占用." 5 | :duplicate-phone "手机已经被占用." 6 | :duplicate-identity "用户名已经被占用." 7 | :duplicate-art-identity "作品id已经被占用." 8 | :duplicate-address "地址已存在." 9 | :duplicate-shipment "该订单运单号已存在." 10 | :duplicate-category "分类已存在." 11 | :duplicate-charge "订单已charge." 12 | :duplicate-college "学校已存在." 13 | :duplicate-device "设备已关联." 14 | :duplicate-favorite "商品已收藏." 15 | :duplicate-inquire "商品已询价." 16 | :duplicate-payment-account "交易帐号已存在." 17 | :duplicate-shipment-detail "运单已经包含商品." 18 | :duplicate-social "社交帐号已存在." 19 | :duplicate-confirmation "验证码已存在." 20 | :db-internal-error "您与bug不期而遇,请反馈给我们,谢谢." 21 | :upload-failed "图片上传失败." 22 | :confirmation-failed "验证失败." 23 | :password-reset-failed "密码重置链接失效." 24 | :app-key-or-secret-wrong "app_key或app_secret不正确." 25 | :email-or-phone-not-exists "邮件或者手机号不存在." 26 | :email-not-exists "邮件不存在." 27 | :phone-not-exists "手机号不存在." 28 | :address-not-exists "地址不存在." 29 | :same-passwords "为了帐号安全,新密码请不要和旧密码相同." 30 | :wrong-password "当前密码不正确." 31 | :institution-artist-name-not-give "请填写艺术家姓名." 32 | :province-city-district-not-match "省市区不吻合." 33 | :wrong-code "验证码无效." 34 | :coupon-not-available "礼券不可用." 35 | :invite-code-not-available "邀请码不可用." 36 | :have-exchanged "邀请码仅开放给新用户或您已兑换过一次" 37 | :unavaliable-share-code "分享码不正确或已失效." 38 | :unvalid-input "输入不正确"} 39 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/lymchat/lymchat/Config.java.example: -------------------------------------------------------------------------------- 1 | package com.lymchat.lymchat; 2 | 3 | import com.facebook.react.bridge.ReactApplicationContext; 4 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * Created by tienson on 9/7/16. 11 | */ 12 | public class Config extends ReactContextBaseJavaModule { 13 | 14 | private static final String APP_KEY = ""; 15 | private static final String APP_SECRET = ""; 16 | private static final String GOOGLE_SIGN_CLIENT_ID = ""; 17 | private static final String WECHAT_ID = ""; 18 | private static final String WECHAT_SECRET = ""; 19 | private static final String WECHAT_STATE = ""; 20 | 21 | public Config(ReactApplicationContext reactContext) { 22 | super(reactContext); 23 | } 24 | 25 | @Override 26 | public String getName() { 27 | return "Config"; 28 | } 29 | 30 | @Override 31 | public Map getConstants() { 32 | final Map constants = new HashMap<>(); 33 | constants.put("app_key", APP_KEY); 34 | constants.put("app_secret", APP_SECRET); 35 | constants.put("google_signin_client_id", GOOGLE_SIGN_CLIENT_ID); 36 | constants.put("wechat_id", WECHAT_ID); 37 | constants.put("wechat_secret", WECHAT_SECRET); 38 | constants.put("wechat_state", WECHAT_STATE); 39 | return constants; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /api/src/api/services/token/crypt.clj: -------------------------------------------------------------------------------- 1 | (ns api.services.token.crypt 2 | (:require [clojure.data.codec.base64 :as b64]) 3 | (:import (javax.crypto KeyGenerator SecretKey Cipher SecretKeyFactory) 4 | (javax.crypto.spec SecretKeySpec PBEKeySpec))) 5 | 6 | (defonce salt "fdjalfjlafjlkajf") 7 | 8 | (defn cipher- [] (. Cipher getInstance "AES")) 9 | (defn aes-keyspec [rawkey] (new SecretKeySpec rawkey "AES")) 10 | 11 | (defn encrypt- 12 | [rawkey plaintext] 13 | (let [cipher (cipher-) 14 | mode (. Cipher ENCRYPT_MODE)] 15 | (. cipher init mode (aes-keyspec rawkey)) 16 | (-> (. cipher doFinal (. plaintext getBytes)) 17 | b64/encode 18 | (String.)))) 19 | 20 | (defn decrypt- 21 | [rawkey ciphertext] 22 | (let [ciphertext (b64/decode (.getBytes ciphertext)) 23 | cipher (cipher-) 24 | mode (. Cipher DECRYPT_MODE)] 25 | (. cipher init mode (aes-keyspec rawkey)) 26 | (new String(. cipher doFinal ciphertext)))) 27 | 28 | (defn passkey 29 | [password & [iterations size]] 30 | (let [keymaker (SecretKeyFactory/getInstance "PBKDF2WithHmacSHA1") 31 | pass (.toCharArray password) 32 | salt (.getBytes salt) 33 | iterations (or iterations 1000) 34 | size (or size 128) 35 | keyspec (PBEKeySpec. pass salt iterations size)] 36 | (-> keymaker (.generateSecret keyspec) .getEncoded))) 37 | 38 | (defn encrypt 39 | [password plaintext] 40 | (encrypt- (passkey password) plaintext)) 41 | 42 | (defn decrypt 43 | [password cyphertext] 44 | (decrypt- (passkey password) cyphertext)) 45 | -------------------------------------------------------------------------------- /api/dev/env.clj: -------------------------------------------------------------------------------- 1 | (ns env 2 | (:require [environ.core :as environ] 3 | [environ-plus.core] 4 | [clojure.edn :as edn] 5 | [clojure.java.io :as io]) 6 | (:import java.io.PushbackReader)) 7 | 8 | (defn- read-config-file [path] 9 | (try 10 | (with-open [r (-> path io/resource io/reader PushbackReader.)] 11 | (edn/read r)) 12 | (catch Exception e 13 | (println (str "WARNING: edn-config: " (.getLocalizedMessage e)))))) 14 | 15 | (defn local-env [environment] 16 | (let [environment (name environment) 17 | config (read-config-file (format "config/%s.edn" environment))] 18 | (merge 19 | config 20 | environ/env 21 | {:environment (keyword environment)}))) 22 | 23 | (defn- switch-env 24 | [environment] 25 | (alter-var-root #'environ-plus.core/env (fn [x] (local-env environment)))) 26 | 27 | (defn switch-to-test! 28 | [] 29 | (switch-env :test)) 30 | 31 | (defn switch-to-development! 32 | [] 33 | (switch-env :development)) 34 | 35 | (defn remove-contacts 36 | [] 37 | (api.db.user/update api.db.util/default-db #uuid "10000000-3c59-4887-995b-cf275db86343" {:contacts []}) 38 | (api.db.user/update api.db.util/default-db #uuid "20000000-81c1-4f1a-aa22-eeb57d2eea98" {:contacts []}) 39 | (api.db.user/update api.db.util/default-db #uuid "30000000-ecf6-484c-bade-4993662742ba" {:contacts []}) 40 | (api.db.user/update api.db.util/default-db #uuid "40000000-ecf6-484c-bade-4993662742ba" {:contacts []}) 41 | 42 | (comment 43 | (do 44 | (switch-to-development!) 45 | (remove-contacts) 46 | )) 47 | 48 | ) 49 | -------------------------------------------------------------------------------- /api/src/api/handler/auth.clj: -------------------------------------------------------------------------------- 1 | (ns api.handler.auth 2 | (:require [api 3 | [schemas :refer :all] 4 | [util :refer [app-auth? doc production? get-avatar get-cdn-path]]] 5 | [api.db 6 | [user :as user] 7 | [util :refer [default-db wrap-temp-username]]] 8 | [api.services.slack :refer [error]] 9 | [api.services.s3 :refer [put-image]] 10 | [api.handler.util :refer [user-token]] 11 | [environ-plus.core :refer [env]] 12 | [plumbing.core :refer [defnk]] 13 | [schema.core :as s] 14 | [api.pg.core :as pg] 15 | [clojure.java.jdbc :as j])) 16 | 17 | (defnk $POST 18 | "Authenticate." 19 | {:responses {200 s/Any 20 | 401 Unauthorized 21 | 400 Wrong}} 22 | [[:request 23 | body :- s/Any 24 | [:custom app-key :- s/Str]]] 25 | (let [body (dissoc body :app-key :app-secret)] 26 | (j/with-db-transaction [db default-db] 27 | (if-let [user (user/get-by-oauth db (:oauth_type body) (:oauth_id body))] 28 | {:body (user-token db user app-key)} 29 | 30 | (let [body (wrap-temp-username body) 31 | new-user (user/create db body) 32 | avatar-name (str (:id new-user))] 33 | ;; s3 upload avatar 34 | (future 35 | (put-image avatar-name (get-avatar (:avatar new-user))) 36 | (user/update db (:id new-user) {:avatar (get-cdn-path avatar-name)})) 37 | {:body (assoc 38 | (user-token db new-user app-key) 39 | :new true)}))))) 40 | -------------------------------------------------------------------------------- /api/src/api/services/twilio.clj: -------------------------------------------------------------------------------- 1 | (ns api.services.twilio 2 | (:require [aleph.http :as http] 3 | [manifold.deferred :as d] 4 | [byte-streams :as bs] 5 | [cheshire.core :refer [generate-string parse-string]] 6 | [environ-plus.core :refer [env]] 7 | [clojure.string :as s] 8 | [api.util :refer [production? base64-encode]] 9 | [taoensso.timbre :as t] 10 | [manifold.time :as mf])) 11 | 12 | (defn base-url [] 13 | (format "https://api.twilio.com/2010-04-01/Accounts/%s/" 14 | (get-in env [:twilio :sid]))) 15 | 16 | (defn fetch-token [] 17 | (try 18 | @(d/chain 19 | (http/post (str (base-url) "Tokens.json") 20 | {:headers {"authorization" (str "Basic " 21 | (base64-encode 22 | (str 23 | (get-in env [:twilio :sid]) 24 | ":" 25 | (get-in env [:twilio :auth-token]))))}}) 26 | :body 27 | bs/to-string 28 | parse-string) 29 | (catch clojure.lang.ExceptionInfo e 30 | (t/error (bs/to-string (:body (ex-data e))))))) 31 | 32 | (defonce token (atom nil)) 33 | 34 | (defn get-ice-servers [] 35 | (get @token "ice_servers")) 36 | 37 | (defn replace-token [] 38 | (future 39 | (let [new-token (fetch-token)] 40 | (reset! token new-token)))) 41 | 42 | (defn run [] 43 | (replace-token) 44 | (mf/every (* 1000 60 30) #'replace-token)) 45 | -------------------------------------------------------------------------------- /api/src/api/schema/util.clj: -------------------------------------------------------------------------------- 1 | (ns api.schema.util 2 | (:require [api.util :refer [strip-id]] 3 | [clojure.string :as str] 4 | [schema.core :as s])) 5 | 6 | (defn optional 7 | [schema] 8 | (some->> schema 9 | (map (fn [[k v]] 10 | [(s/optional-key k) v]) ) 11 | (into {}))) 12 | 13 | (defn part 14 | "When schema used for update, we only need id to be required. 15 | Example: (part User :excludes [:id]) 16 | (part User :only [:email :password])" 17 | ([schema] 18 | (part schema nil)) 19 | ([schema {:keys [excludes only must] :as opts}] 20 | (let [ks (if only only (remove (set excludes) (keys schema)))] 21 | (into {} 22 | (map (fn [[k v]] 23 | (if (or (= :id k) (contains? (set must) k)) 24 | [k v] 25 | [(s/optional-key k) v])) 26 | (select-keys schema ks)))))) 27 | 28 | (defmacro without-id 29 | [schema] 30 | `(part ~schema {:excludes [:id]})) 31 | 32 | (defn without-id-and-cu-time [schema] 33 | (part schema {:excludes [:id :updated_at :created_at]})) 34 | 35 | (defn without-uid 36 | [schema] 37 | (dissoc schema :user_id)) 38 | 39 | (defn to-id 40 | [x] 41 | (-> x 42 | (str "_id") 43 | str/lower-case 44 | keyword)) 45 | 46 | (defmacro wrap 47 | [f s] 48 | `(let [s# ~s 49 | id# (to-id '~s)] 50 | (-> ~f 51 | (dissoc id#) 52 | (assoc (strip-id id#) (part s#)) 53 | part))) 54 | 55 | (defn wrap-uid 56 | [body user-id] 57 | (assoc body :user_id user-id)) 58 | 59 | (defn wrap-any 60 | [schema] 61 | (assoc schema s/Keyword s/Any)) 62 | -------------------------------------------------------------------------------- /api/src/api/pg/pgpass.clj: -------------------------------------------------------------------------------- 1 | (ns api.pg.pgpass 2 | "Logic for matching passwords ~/.pgpass passwords to db specs." 3 | (:require [clojure.java.io :as io] 4 | [clojure.string :as str])) 5 | 6 | (defn parse-pgpass-line 7 | "The .pgpass files has lines of format: hostname:port:database:username:password 8 | Return a map of fields {:pg-hostname \"*\" ...}" 9 | [s] 10 | (zipmap 11 | [:pg-hostname :pg-port :pg-database :pg-username :pg-password] 12 | (str/split s #":"))) 13 | 14 | (defn read-pgpass 15 | "Find ~/.pgpass, read it and parse lines into maps" 16 | [] 17 | (let [homedir (io/file (System/getProperty "user.home")) 18 | passfile (io/file homedir ".pgpass")] 19 | (when (.isFile passfile) 20 | (with-open [r (io/reader passfile)] 21 | (->> r 22 | line-seq 23 | (map parse-pgpass-line) 24 | doall))))) 25 | 26 | (defn pgpass-matches? 27 | "(filter (partial pgpass-matches? spec) pgpass-lines)" 28 | [{:keys [host port dbname user]} {:keys [pg-hostname pg-port pg-database pg-username pg-password]}] 29 | (when 30 | (and 31 | (or (= pg-hostname "*") (= pg-hostname host) (and (= pg-hostname "localhost") (nil? host))) 32 | (or (= pg-port "*") (= pg-port port) (and (= pg-port "5432") (nil? port))) 33 | (or (= pg-database "*") (= pg-database dbname)) 34 | (or (= pg-username "*") (= pg-username user))) 35 | pg-password)) 36 | 37 | (defn pgpass-lookup 38 | "Look up password from ~/.pgpass based on db spec {:host ... :port ... :dbname ... :user ...}" 39 | [spec] 40 | (when-let [match (first (filter (partial pgpass-matches? spec) (read-pgpass)))] 41 | (:pg-password match))) 42 | -------------------------------------------------------------------------------- /api/src/api/db/invite.clj: -------------------------------------------------------------------------------- 1 | (ns api.db.invite 2 | (:require [clojure.java.jdbc :as j] 3 | [api.db.util :refer [with-now]] 4 | [api.db.user :as user])) 5 | 6 | (defonce ^:private table "invites") 7 | 8 | (defn create 9 | "user-id want to be friend with invite-id." 10 | [db user-id invite-id] 11 | (try (j/insert! db table (with-now {:user_id user-id 12 | :invite_id invite-id} 13 | [:created_at])) 14 | (catch Exception e 15 | nil))) 16 | 17 | (defn delete 18 | [db user-id invite-id] 19 | (j/delete! db table ["user_id = ? and invite_id = ?" user-id invite-id])) 20 | 21 | (defn accept 22 | "invite-id accept user-id's invitation." 23 | [db invite-id user-id] 24 | (delete db user-id invite-id) 25 | (user/add-contact db invite-id user-id) 26 | (user/add-contact db user-id invite-id)) 27 | 28 | (defn reject 29 | "invite-id reject user-id's invitation." 30 | [db invite-id user-id] 31 | (delete db user-id invite-id)) 32 | 33 | (defn get-my-invites 34 | [db user-id] 35 | (j/query db 36 | ["select u.id, u.username, u.name, u.avatar, u.language, u.timezone, i.created_at from users as u 37 | left join invites as i on u.id = i.user_id 38 | where i.invite_id = ? order by i.created_at desc" user-id])) 39 | 40 | 41 | (comment 42 | (let [users (:users seeds.db/seeds)] 43 | (def db api.db.util/default-db) 44 | (def id1 (:id (nth users 0))) 45 | (def id2 (:id (nth users 1))) 46 | (def id3 (:id (nth users 2))) 47 | (def id4 (:id (nth users 3))) 48 | (create db id1 id2) 49 | (create db id1 id3) 50 | (create db id4 id1) 51 | (accept db id2 id1) 52 | )) 53 | -------------------------------------------------------------------------------- /api/src/api/handler/util.clj: -------------------------------------------------------------------------------- 1 | (ns api.handler.util 2 | (:require [api.db.user :as user] 3 | [api.db.channel :as channel] 4 | [api.db.message :as message] 5 | [api.handler.ws :as ws] 6 | [api.middlewares.auth :refer [build-token]] 7 | [environ-plus.core :refer [env]] 8 | [clj-time.core :as t] 9 | [api.util :refer [flake-id]])) 10 | 11 | (defn user-token 12 | ([db user app-key] 13 | (user-token db user app-key (t/years 10))) 14 | ([db user app-key expires] 15 | {:user user 16 | :token (build-token (str (:id user)) {:expires expires 17 | :app-key app-key})})) 18 | 19 | (defn admin-token 20 | [db] 21 | {:token (build-token "10000000-3c59-4887-995b-cf275db86343" 22 | {:expires (t/months 2)})}) 23 | 24 | (defn success? 25 | [r] 26 | {:pre (integer? (:status r))} 27 | (<= 200 (:status r) 299)) 28 | 29 | (defn join 30 | [db id user-id] 31 | (when-not (channel/member-exists? db id user-id) 32 | (channel/join db id user-id) 33 | (channel/inc-members db id) 34 | (user/add-channel db user-id id) 35 | ;; ws notification 36 | (when-let [user (user/get db user-id)] 37 | (let [msg {:id (flake-id) 38 | :channel_id (str id) 39 | :body "joined, welcome!" 40 | :name (:name user) 41 | :username (:username user) 42 | :avatar (:avatar user) 43 | :user_id (str user-id) 44 | :language (:language user) 45 | :timezone (:timezone user)}] 46 | (message/channels-create (:redis env) msg) 47 | (ws/send-channel-message msg))))) 48 | -------------------------------------------------------------------------------- /android/app/BUCK: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # To learn about Buck see [Docs](https://buckbuild.com/). 4 | # To run your application with Buck: 5 | # - install Buck 6 | # - `npm start` - to start the packager 7 | # - `cd android` 8 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US` 9 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 10 | # - `buck install -r android/app` - compile, install and run application 11 | # 12 | 13 | lib_deps = [] 14 | for jarfile in glob(['libs/*.jar']): 15 | name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) 16 | lib_deps.append(':' + name) 17 | prebuilt_jar( 18 | name = name, 19 | binary_jar = jarfile, 20 | ) 21 | 22 | for aarfile in glob(['libs/*.aar']): 23 | name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) 24 | lib_deps.append(':' + name) 25 | android_prebuilt_aar( 26 | name = name, 27 | aar = aarfile, 28 | ) 29 | 30 | android_library( 31 | name = 'all-libs', 32 | exported_deps = lib_deps 33 | ) 34 | 35 | android_library( 36 | name = 'app-code', 37 | srcs = glob([ 38 | 'src/main/java/**/*.java', 39 | ]), 40 | deps = [ 41 | ':all-libs', 42 | ':build_config', 43 | ':res', 44 | ], 45 | ) 46 | 47 | android_build_config( 48 | name = 'build_config', 49 | package = 'com.lymchat.lymchat', 50 | ) 51 | 52 | android_resource( 53 | name = 'res', 54 | res = 'src/main/res', 55 | package = 'com.lymchat.lymchat', 56 | ) 57 | 58 | android_binary( 59 | name = 'app', 60 | package_type = 'debug', 61 | manifest = 'src/main/AndroidManifest.xml', 62 | keystore = '//android/keystores:debug', 63 | deps = [ 64 | ':app-code', 65 | ], 66 | ) 67 | -------------------------------------------------------------------------------- /api/src/api/services/exponent.clj: -------------------------------------------------------------------------------- 1 | (ns api.services.exponent 2 | (:require [aleph.http :as http] 3 | [manifold.deferred :as d] 4 | [byte-streams :as bs] 5 | [cheshire.core :refer [generate-string parse-string]] 6 | [environ-plus.core :refer [env]] 7 | [clojure.string :as s] 8 | [api.util :refer [production?]] 9 | [taoensso.timbre :as t] 10 | [ring.util.codec :refer [url-encode]])) 11 | 12 | (defonce api-host "https://exp.host/--/api/notify/") 13 | 14 | (defn post 15 | [url body] 16 | (http/post url 17 | (cond-> 18 | {:headers {"content-type" "application/json"}} 19 | body 20 | (assoc :body (generate-string body))))) 21 | 22 | (defn build-params 23 | [opts] 24 | (-> [opts] 25 | (generate-string) 26 | (url-encode "UTF-8"))) 27 | 28 | (defn add-device 29 | "If success, return the player id." 30 | [device-token] 31 | (let [params (build-params {:exponentPushToken device-token})] 32 | (try 33 | @(d/chain 34 | (post (str api-host params) nil) 35 | :body 36 | bs/to-string 37 | parse-string 38 | (fn [result] 39 | (prn {:result result}))) 40 | (catch clojure.lang.ExceptionInfo e 41 | (t/error e (bs/to-string (:body (ex-data e)))))))) 42 | 43 | (defn send-notification 44 | [token message data] 45 | (let [m {:exponentPushToken token 46 | :message message 47 | :data data} 48 | params (build-params m)] 49 | (try 50 | (d/chain 51 | (post (str api-host params) data) 52 | :body 53 | bs/to-string 54 | (fn [res] 55 | (parse-string res true))) 56 | (catch clojure.lang.ExceptionInfo e 57 | (t/error e (bs/to-string (:body (ex-data e)))))))) 58 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - FirebaseAnalytics (3.2.1): 3 | - FirebaseInstanceID (~> 1.0) 4 | - GoogleInterchangeUtilities (~> 1.2) 5 | - GoogleSymbolUtilities (~> 1.1) 6 | - GoogleUtilities (~> 1.3) 7 | - FirebaseInstanceID (1.0.7) 8 | - Google/Core (3.0.3): 9 | - FirebaseAnalytics (~> 3.2) 10 | - Google/SignIn (3.0.3): 11 | - Google/Core 12 | - GoogleSignIn (~> 4.0) 13 | - GoogleAppUtilities (1.1.1): 14 | - GoogleSymbolUtilities (~> 1.0) 15 | - GoogleAuthUtilities (2.0.1): 16 | - GoogleNetworkingUtilities (~> 1.0) 17 | - GoogleSymbolUtilities (~> 1.0) 18 | - GoogleInterchangeUtilities (1.2.1): 19 | - GoogleSymbolUtilities (~> 1.0) 20 | - GoogleNetworkingUtilities (1.2.1): 21 | - GoogleSymbolUtilities (~> 1.0) 22 | - GoogleSignIn (4.0.0): 23 | - GoogleAppUtilities (~> 1.1) 24 | - GoogleAuthUtilities (~> 2.0) 25 | - GoogleNetworkingUtilities (~> 1.2) 26 | - GoogleUtilities (~> 1.3) 27 | - GoogleSymbolUtilities (1.1.1) 28 | - GoogleUtilities (1.3.1): 29 | - GoogleSymbolUtilities (~> 1.0) 30 | 31 | DEPENDENCIES: 32 | - Google/SignIn 33 | 34 | SPEC CHECKSUMS: 35 | FirebaseAnalytics: 0fd6532cb2c3d03cd5cf26ad295ccb091efd3104 36 | FirebaseInstanceID: a9d923f3d0b6fbf9fac89310860357aaadc34be5 37 | Google: 813c467362eabc11385f5a5cc9ad0cb651a58f4e 38 | GoogleAppUtilities: f1730f91f67767b453c289577040dd3f425ffdc7 39 | GoogleAuthUtilities: d7a19615cd7627688afcf181737f1011ee8b5a23 40 | GoogleInterchangeUtilities: def8415a862effc67d549d5b5b0b9c7a2f97d4de 41 | GoogleNetworkingUtilities: 3e83269048cfb498dc7ec83ab36813360965c74f 42 | GoogleSignIn: 09036ed61f8e75f1424100d63f7719480b2428c3 43 | GoogleSymbolUtilities: 33117db1b5f290c6fbf259585e4885b4c84b98d7 44 | GoogleUtilities: 56c5ac05b7aa5dc417a1bb85221a9516e04d7032 45 | 46 | PODFILE CHECKSUM: 5ff54beea3a4067c978549774d111e1fbb930f9a 47 | 48 | COCOAPODS: 1.0.1 49 | -------------------------------------------------------------------------------- /api/resources/seeds/topics.edn: -------------------------------------------------------------------------------- 1 | [{:id 1 2 | :user_id #uuid "10000000-3c59-4887-995b-cf275db86343" 3 | :name "Travel" 4 | :en_name "Travel" 5 | :type "interest"} 6 | {:id 2 7 | :user_id #uuid "10000000-3c59-4887-995b-cf275db86343" 8 | :name "Food" 9 | :en_name "Food" 10 | :type "interest"} 11 | {:id 3 12 | :user_id #uuid "10000000-3c59-4887-995b-cf275db86343" 13 | :name "Book" 14 | :en_name "Book" 15 | :type "interest"} 16 | {:id 4 17 | :user_id #uuid "10000000-3c59-4887-995b-cf275db86343" 18 | :name "Music" 19 | :en_name "Music" 20 | :type "interest"} 21 | {:id 5 22 | :user_id #uuid "10000000-3c59-4887-995b-cf275db86343" 23 | :name "Movie" 24 | :en_name "Movie" 25 | :type "interest"} 26 | {:id 6 27 | :user_id #uuid "10000000-3c59-4887-995b-cf275db86343" 28 | :name "Sports" 29 | :en_name "Sports" 30 | :type "interest"} 31 | {:id 7 32 | :user_id #uuid "10000000-3c59-4887-995b-cf275db86343" 33 | :name "Yunnan, China" 34 | :en_name "Yunnan, China" 35 | :type "location"} 36 | {:id 8 37 | :user_id #uuid "10000000-3c59-4887-995b-cf275db86343" 38 | :name "Chiang Mai, Thailand" 39 | :en_name "Chiang Mai, Thailand" 40 | :type "location"} 41 | {:id 9 42 | :user_id #uuid "10000000-3c59-4887-995b-cf275db86343" 43 | :name "Bangkok, Thailand" 44 | :en_name "Bangkok, Thailand" 45 | :type "location"} 46 | {:id 10 47 | :user_id #uuid "10000000-3c59-4887-995b-cf275db86343" 48 | :name "Berlin, German" 49 | :en_name "Berlin, German" 50 | :type "location"} 51 | {:id 11 52 | :user_id #uuid "10000000-3c59-4887-995b-cf275db86343" 53 | :name "Spanish" 54 | :en_name "Spanish" 55 | :type "language"} 56 | {:id 12 57 | :user_id #uuid "10000000-3c59-4887-995b-cf275db86343" 58 | :name "Japanese" 59 | :en_name "Japanese" 60 | :type "language"} 61 | {:id 13 62 | :user_id #uuid "10000000-3c59-4887-995b-cf275db86343" 63 | :name "Chinese" 64 | :en_name "Chinese" 65 | :type "language"}] 66 | -------------------------------------------------------------------------------- /src/lymchat/fs.cljs: -------------------------------------------------------------------------------- 1 | (ns lymchat.fs) 2 | 3 | (def fs (js/require "react-native-fs")) 4 | 5 | (def documents-path (.-DocumentDirectoryPath fs)) 6 | 7 | (defn path 8 | [file-path] 9 | (str documents-path file-path)) 10 | 11 | (defn get-info 12 | [] 13 | (-> 14 | (.getFSInfo fs) 15 | (.then (fn [result] 16 | (prn result))) 17 | (.catch (fn [err] 18 | (prn err))))) 19 | 20 | (defn list-files 21 | ([] 22 | (list-files nil)) 23 | ([k] 24 | (-> 25 | (if (= :verbose k) 26 | (.readDir fs documents-path) 27 | (.readdir fs documents-path)) 28 | (.then (fn [result] 29 | (prn (js->clj result :keywordize-keys true)))) 30 | (.catch (fn [err] 31 | (prn err)))))) 32 | 33 | (defn download 34 | [url path success-cb error-cb] 35 | (-> 36 | (.downloadFile fs #js {:fromUrl url 37 | :toFile path}) 38 | (.then (fn [result] 39 | (if (= 200 (aget result "statusCode")) 40 | (success-cb) 41 | (error-cb)))) 42 | (.catch (fn [err] 43 | (prn err))))) 44 | 45 | (defn delete 46 | [path] 47 | (-> 48 | (.unlink fs path) 49 | (.then (fn [result] 50 | (prn result))) 51 | (.catch (fn [err] 52 | (prn err))))) 53 | 54 | (defn file-exists? 55 | [path true-cb false-cb] 56 | (-> 57 | (.exists fs path) 58 | (.then (fn [result] 59 | (if result 60 | (true-cb) 61 | (false-cb)))) 62 | (.catch (fn [err] 63 | (prn err))))) 64 | 65 | ;; ctime mtime isFile 66 | (defn stat 67 | [path] 68 | (-> 69 | (.stat fs path) 70 | (.then (fn [result] 71 | (prn result))) 72 | (.catch (fn [err] 73 | (prn err))))) 74 | 75 | (defn copy 76 | [from to] 77 | (-> 78 | (.copyFile fs from to) 79 | (.then (fn [result] 80 | (prn result))) 81 | (.catch (fn [err] 82 | (prn err))))) 83 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | # We fork some components by platform. 4 | .*/*[.]android.js 5 | 6 | # Ignore templates with `@flow` in header 7 | .*/local-cli/generator.* 8 | 9 | # Ignore malformed json 10 | .*/node_modules/y18n/test/.*\.json 11 | 12 | # Ignore the website subdir 13 | /website/.* 14 | 15 | # Ignore BUCK generated dirs 16 | /\.buckd/ 17 | 18 | # Ignore unexpected extra @providesModule 19 | .*/node_modules/commoner/test/source/widget/share.js 20 | 21 | # Ignore duplicate module providers 22 | # For RN Apps installed via npm, "Libraries" folder is inside node_modules/react-native but in the source repo it is in the root 23 | .*/Libraries/react-native/React.js 24 | .*/Libraries/react-native/ReactNative.js 25 | .*/node_modules/jest-runtime/build/__tests__/.* 26 | 27 | [include] 28 | 29 | [libs] 30 | node_modules/react-native/Libraries/react-native/react-native-interface.js 31 | node_modules/react-native/flow 32 | flow/ 33 | 34 | [options] 35 | module.system=haste 36 | 37 | esproposal.class_static_fields=enable 38 | esproposal.class_instance_fields=enable 39 | 40 | experimental.strict_type_args=true 41 | 42 | munge_underscores=true 43 | 44 | module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' 45 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 46 | 47 | suppress_type=$FlowIssue 48 | suppress_type=$FlowFixMe 49 | suppress_type=$FixMe 50 | 51 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(30\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 52 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(30\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 53 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 54 | 55 | unsafe.enable_getters_and_setters=true 56 | 57 | [version] 58 | ^0.30.0 59 | -------------------------------------------------------------------------------- /api/src/api/handler/channel.clj: -------------------------------------------------------------------------------- 1 | (ns api.handler.channel 2 | (:require [api 3 | [schemas :refer :all] 4 | [util :refer [doc]]] 5 | [api.db 6 | [channel :as channel] 7 | [user :as user] 8 | [message :as message] 9 | [util :refer [default-db]]] 10 | [api.handler.ws :as ws] 11 | [api.handler.util :refer [join]] 12 | [api.services.slack :refer [error]] 13 | [environ-plus.core :refer [env]] 14 | [plumbing.core :refer [defnk]] 15 | [schema.core :as s] 16 | [clojure.java.jdbc :as j] 17 | [api.pg.core :as pg])) 18 | 19 | (defnk $:id$join$GET 20 | "Join channel." 21 | {:responses {200 s/Any 22 | 401 Unauthorized 23 | 400 Wrong 24 | 404 NotFound}} 25 | [[:request 26 | [:uri-args id :- ID] 27 | [:custom user-id :- ID]]] 28 | (j/with-db-transaction [db default-db] 29 | (join db id user-id) 30 | {:body {:messages (message/get-channel-latest-messages (:redis env) id)}})) 31 | 32 | (defnk $:id$leave$GET 33 | "Leave channel." 34 | {:responses {200 Ack 35 | 401 Unauthorized 36 | 400 Wrong 37 | 404 NotFound}} 38 | [[:request 39 | [:uri-args id :- ID] 40 | [:custom user-id :- ID]]] 41 | (j/with-db-transaction [db default-db] 42 | (when (channel/member-exists? db id user-id) 43 | (channel/leave db id user-id) 44 | (channel/dec-members db id) 45 | (user/remove-channel db user-id id) 46 | (when-let [user (user/get db user-id)] 47 | (ws/send-channel-message 48 | {:channel_id (str id) 49 | :body "left" 50 | :name (:name user) 51 | :username (:username user) 52 | :avatar (:avatar user) 53 | :user_id (str user-id) 54 | :language (:language user) 55 | :timezone (:timezone user)})))) 56 | ;; ws notification 57 | ack) 58 | -------------------------------------------------------------------------------- /api/src/api/schema/coerce.clj: -------------------------------------------------------------------------------- 1 | (ns api.schema.coerce 2 | (:require [schema.core :as s] 3 | [api.schemas :as as] 4 | [clj-time.coerce :as coerce] 5 | [hiccup.util :refer [escape-html]] 6 | [api.util :refer [->long ->double mobile-display mobile-request?]])) 7 | 8 | (defonce ^:private request-non-escape-rules 9 | {:arts :description}) 10 | 11 | ;; (defn escape-if-match 12 | ;; [request x] 13 | ;; (let [{:keys [uri body]} request 14 | ;; [_ e] (re-find #"/v[\d]+/(\w+)" uri) 15 | ;; e (keyword e)] 16 | ;; (if-let [k (get request-non-escape-rules e)] 17 | ;; (if (and (string? x) (= (get body k) x)) 18 | ;; x 19 | ;; (escape-html x)) 20 | ;; (escape-html x)))) 21 | 22 | (def input-coercer 23 | (fn [schema] 24 | (fn [request x] 25 | (cond 26 | (contains? #{"null" "nil" ""} x) 27 | (if (= java.lang.String schema) "" nil) 28 | 29 | (= 'Int 30 | (s/explain schema)) 31 | (->long x) 32 | 33 | (= java.util.UUID schema) 34 | (java.util.UUID/fromString x) 35 | 36 | (= java.lang.Number 37 | (s/explain schema)) 38 | (->double x) 39 | 40 | (= java.lang.String schema) 41 | ;; escape html chars 42 | x 43 | ;; (escape-if-match request x) 44 | 45 | (= java.lang.Boolean 46 | schema) 47 | (if (or (true? x) (= x "true")) true false) 48 | 49 | (= java.util.Date 50 | schema) 51 | (-> x 52 | coerce/from-string 53 | coerce/to-sql-time) 54 | 55 | (= java.sql.Date 56 | schema) 57 | (-> x 58 | coerce/from-string 59 | coerce/to-sql-date) 60 | 61 | (= java.sql.Timestamp 62 | schema) 63 | (-> x 64 | coerce/from-string 65 | coerce/to-sql-time) 66 | 67 | :else 68 | x)))) 69 | 70 | (def output-coercer 71 | (fn [schema] 72 | (fn [request x] 73 | x))) 74 | -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.framework/Headers/GIDSignInButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * GIDSignInButton.h 3 | * Google Sign-In iOS SDK 4 | * 5 | * Copyright 2012 Google Inc. 6 | * 7 | * Use of this SDK is subject to the Google APIs Terms of Service: 8 | * https://developers.google.com/terms/ 9 | */ 10 | 11 | #import 12 | 13 | // The various layout styles supported by the GIDSignInButton. 14 | // The minimum size of the button depends on the language used for text. 15 | // The following dimensions (in points) fit for all languages: 16 | // kGIDSignInButtonStyleStandard: 230 x 48 17 | // kGIDSignInButtonStyleWide: 312 x 48 18 | // kGIDSignInButtonStyleIconOnly: 48 x 48 (no text, fixed size) 19 | typedef NS_ENUM(NSInteger, GIDSignInButtonStyle) { 20 | kGIDSignInButtonStyleStandard = 0, 21 | kGIDSignInButtonStyleWide = 1, 22 | kGIDSignInButtonStyleIconOnly = 2 23 | }; 24 | 25 | // The various color schemes supported by the GIDSignInButton. 26 | typedef NS_ENUM(NSInteger, GIDSignInButtonColorScheme) { 27 | kGIDSignInButtonColorSchemeDark = 0, 28 | kGIDSignInButtonColorSchemeLight = 1 29 | }; 30 | 31 | // This class provides the "Sign in with Google" button. You can instantiate this 32 | // class programmatically or from a NIB file. You should set up the 33 | // |GIDSignIn| shared instance with your client ID and any additional scopes, 34 | // implement the delegate methods for |GIDSignIn|, and add this button to your 35 | // view hierarchy. 36 | @interface GIDSignInButton : UIControl 37 | 38 | // The layout style for the sign-in button. 39 | // Possible values: 40 | // - kGIDSignInButtonStyleStandard: 230 x 48 (default) 41 | // - kGIDSignInButtonStyleWide: 312 x 48 42 | // - kGIDSignInButtonStyleIconOnly: 48 x 48 (no text, fixed size) 43 | @property(nonatomic, assign) GIDSignInButtonStyle style; 44 | 45 | // The color scheme for the sign-in button. 46 | // Possible values: 47 | // - kGIDSignInButtonColorSchemeDark 48 | // - kGIDSignInButtonColorSchemeLight (default) 49 | @property(nonatomic, assign) GIDSignInButtonColorScheme colorScheme; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /api/src/api/db/stats.clj: -------------------------------------------------------------------------------- 1 | (ns api.db.stats 2 | (:require [taoensso.carmine :as car] 3 | [api.util :refer [wcar*]] 4 | [clojure.java.jdbc :as j] 5 | [environ-plus.core :refer [env]] 6 | [clj-time.core :refer [now]] 7 | [api.db.util :refer [default-db]] 8 | [clj-time.coerce :as cc] 9 | [clj-time.format :as f] 10 | [manifold.time :as mf])) 11 | 12 | (defonce ^:private messages-key "stats:messages") 13 | (defonce ^:private photos-key "stats:photos") 14 | 15 | ;; TODO rejected, how long 16 | (defonce ^:private videos-key "stats:videos") 17 | 18 | (defn- today 19 | [] 20 | (f/unparse (f/formatter "yyyy-MM-dd") (now))) 21 | 22 | (defn inc-point 23 | [db type] 24 | (when-let [key (case type 25 | :message messages-key 26 | :photo photos-key 27 | :video videos-key 28 | nil)] 29 | (wcar* db 30 | (car/hincrby key (today) 1)))) 31 | 32 | (defn sync->pg 33 | [] 34 | (let [redis (:redis env) 35 | day (today) 36 | stats (->> 37 | (wcar* redis 38 | (car/hget messages-key day) 39 | (car/hget photos-key day) 40 | (car/hget videos-key day)) 41 | (map (fn [s] (if (nil? s) 0 (Integer/parseInt s)))))] 42 | (j/execute! default-db 43 | ["insert into stats (type,day,value) values (?,?,?) ON CONFLICT (type,day) DO UPDATE SET value = ?;" 44 | "messages" (cc/to-sql-date day) (nth stats 0) (nth stats 0)]) 45 | (j/execute! default-db 46 | ["insert into stats (type,day,value) values (?,?,?) ON CONFLICT (type,day) DO UPDATE SET value = ?;" 47 | "photos" (cc/to-sql-date day) (nth stats 1) (nth stats 1)]) 48 | (j/execute! default-db 49 | ["insert into stats (type,day,value) values (?,?,?) ON CONFLICT (type,day) DO UPDATE SET value = ?;" 50 | "videos" (cc/to-sql-date day) (nth stats 2) (nth stats 2)]))) 51 | 52 | ;; executed every 10 minutes 53 | (defn run 54 | [] 55 | (mf/every (* 1000 60 10) #'sync->pg)) 56 | -------------------------------------------------------------------------------- /api/src/api/flake/utils.clj: -------------------------------------------------------------------------------- 1 | (ns api.flake.utils 2 | (:require [primitive-math :as p]) 3 | (:import [java.net NetworkInterface] 4 | [java.nio ByteBuffer])) 5 | 6 | (def ^{:const true} one-million 1000000) 7 | 8 | (def ^{:const true :private true} 9 | base62-alphabet 10 | "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") 11 | 12 | (defn encode 13 | "Encodes n in a new base of ks." 14 | [ks n] 15 | (let [base (count ks)] 16 | (->> (iterate #(quot % base) n) 17 | (take-while pos?) 18 | (reduce (fn [acc n] 19 | (conj acc (nth ks (mod n base)))) 20 | nil) 21 | (apply str)))) 22 | 23 | (def ^{:doc "Encodes a given value into a base62 representation."} 24 | base62-encode 25 | (partial encode base62-alphabet)) 26 | 27 | (defn byte-buffer 28 | ^{:tag java.nio.ByteBuffer 29 | :doc "Returns a ByteBuffer allocated to be size."} 30 | [size] 31 | (ByteBuffer/allocate size)) 32 | 33 | (defn now 34 | "Returns the current Unix time in milliseconds." 35 | [] 36 | (System/currentTimeMillis)) 37 | 38 | ;; Borrowed from Skuld--thanks, Kyle! 39 | (defn epoch 40 | "Returns the delta between the Unix time in milliseconds and nanoTime." 41 | [] 42 | (-> one-million 43 | (* (now)) 44 | (- (System/nanoTime)))) 45 | 46 | (defn epoch-mean 47 | "Takes N samples of epoch and returns the mean." 48 | [n] 49 | (-> (->> (repeatedly epoch) 50 | (map double) 51 | (take n) 52 | (reduce +)) 53 | (/ n) 54 | long)) 55 | 56 | (defn now-from-epoch 57 | "Returns the estimated current time in milliseconds from epoch." 58 | [e] 59 | (-> ^long e 60 | (p/+ (System/nanoTime)) 61 | (p/div one-million))) 62 | 63 | (defn get-hardware-addresses 64 | "Returns a sequence of hardware addresses (generally MACs) formatted as byte 65 | arrays which have been filtered such that they do not contain nil or 66 | `0.0.0.0.0.0`." 67 | [] 68 | (->> (NetworkInterface/getNetworkInterfaces) 69 | enumeration-seq 70 | (map (fn [^NetworkInterface ni] (.getHardwareAddress ni))) 71 | (filter identity) 72 | (remove #(every? zero? %)))) 73 | -------------------------------------------------------------------------------- /api/src/api/handler/me.clj: -------------------------------------------------------------------------------- 1 | (ns api.handler.me 2 | (:require [api 3 | [schemas :refer :all] 4 | [util :refer [doc]]] 5 | [api.db 6 | [user :as user] 7 | [channel :as channel] 8 | [mention :as mention] 9 | [util :refer [default-db]]] 10 | [api.services.slack :refer [error]] 11 | [api.handler.util :refer [join]] 12 | [environ-plus.core :refer [env]] 13 | [plumbing.core :refer [defnk]] 14 | [schema.core :as s] 15 | [clojure.java.jdbc :as j] 16 | [clojure.string :as str])) 17 | 18 | (defnk $PATCH 19 | "Update current user" 20 | {:responses {200 Ack 21 | 400 Wrong 22 | 401 Unauthorized 23 | 404 Missing}} 24 | [[:request 25 | [:custom user-id :- ID] 26 | body :- UserPatch]] 27 | (j/with-db-connection [db default-db] 28 | (let [result (user/update db user-id body)] 29 | (when (:username body) 30 | ;; join lym channel 31 | (join db #uuid "10000000-3c59-4887-995b-cf275db86343" user-id)) 32 | (if (= [:error :duplicated] result) 33 | (bad "Duplicate!") 34 | ack)))) 35 | 36 | (defnk $contacts$:id$DELETE 37 | "Get contacts." 38 | {:responses {200 s/Any 39 | 404 Missing}} 40 | [[:request 41 | [:uri-args id :- ID] 42 | [:custom user-id :- ID]]] 43 | (j/with-db-connection [db default-db] 44 | (user/remove-contact db user-id id) 45 | ack)) 46 | 47 | (defnk $channels$search$GET 48 | "Get matches." 49 | {:responses {200 s/Any 50 | 404 Missing}} 51 | [[:request 52 | [:query-params 53 | q :- s/Str 54 | {limit :- s/Int 5}] 55 | [:custom user-id :- ID]]] 56 | {:body {:channels (channel/search-by-name-prefix q limit)}}) 57 | 58 | (defnk $mentions$GET 59 | "Get mentions." 60 | {:responses {200 s/Any 61 | 404 Missing}} 62 | [[:request 63 | [:query-params 64 | {before-id :- s/Int nil} 65 | {after-id :- s/Int nil} 66 | {limit :- s/Int 20}] 67 | [:custom user-id :- ID]]] 68 | {:body {:mentions (mention/get (:redis env) user-id {:before-id before-id 69 | :after-id after-id 70 | :limit limit})}}) 71 | -------------------------------------------------------------------------------- /ios/LymchatTests/LymchatTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | #import 12 | 13 | #import "RCTLog.h" 14 | #import "RCTRootView.h" 15 | 16 | #define TIMEOUT_SECONDS 600 17 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 18 | 19 | @interface LymchatTests : XCTestCase 20 | 21 | @end 22 | 23 | @implementation LymchatTests 24 | 25 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 26 | { 27 | if (test(view)) { 28 | return YES; 29 | } 30 | for (UIView *subview in [view subviews]) { 31 | if ([self findSubviewInView:subview matching:test]) { 32 | return YES; 33 | } 34 | } 35 | return NO; 36 | } 37 | 38 | - (void)testRendersWelcomeScreen 39 | { 40 | UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; 41 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 42 | BOOL foundElement = NO; 43 | 44 | __block NSString *redboxError = nil; 45 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 46 | if (level >= RCTLogLevelError) { 47 | redboxError = message; 48 | } 49 | }); 50 | 51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 54 | 55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 57 | return YES; 58 | } 59 | return NO; 60 | }]; 61 | } 62 | 63 | RCTSetLogFunction(RCTDefaultLogFunction); 64 | 65 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 66 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 67 | } 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /api/src/api/db/notification.clj: -------------------------------------------------------------------------------- 1 | (ns api.db.notification 2 | (:require [taoensso.carmine :as car] 3 | [taoensso.timbre :as t] 4 | [api.util :refer [wcar*]] 5 | ;; [api.services.onesignal :as os] 6 | [api.services.exponent :as exp] 7 | [environ-plus.core :refer [env]])) 8 | 9 | (defonce ^:private redis-key "notifications") 10 | 11 | ;; migrate to exponent 12 | (defn add-device 13 | [db user-id token] 14 | (when (and user-id token) 15 | (let [tokens (wcar* db (car/hget redis-key (str user-id)))] 16 | (wcar* db (car/hset redis-key (str user-id) (set (conj tokens token))))))) 17 | 18 | ;; (defn add-device 19 | ;; [db user-id device-token] 20 | ;; (when (and user-id device-token) 21 | ;; (if-let [player-id (os/add-device device-token)] 22 | ;; (wcar* db 23 | ;; (car/hset redis-key (str user-id) player-id)) 24 | ;; (t/error "Add device token failed: " {:user-id user-id 25 | ;; :device-token device-token})))) 26 | 27 | (defn send-notification 28 | [db user-id message data] 29 | (let [tokens (wcar* db (car/hget redis-key (str user-id)))] 30 | (when (set? tokens) 31 | (doseq [token tokens] 32 | (when token 33 | (exp/send-notification token message data)))))) 34 | 35 | ;; (defn reset-badge 36 | ;; [db user-id] 37 | ;; (if-let [player-id (wcar* db (car/hget redis-key (str user-id)))] 38 | ;; (os/reset-badge player-id) 39 | ;; (t/error "Notification Error: can't find player-id of " user-id))) 40 | 41 | (comment 42 | (send-notification (:redis env) 43 | "bdc69d56-689f-428b-b5e8-7a4a30d9861b" 44 | "Hi, test" 45 | {:type "bingo" 46 | :name "tienson"}) 47 | 48 | (send-notification (:redis env) 49 | "10000000-3c59-4887-995b-cf275db86343" 50 | "Tim Duncan: Hi tienson" 51 | {:type "new-message" 52 | :message {:id (api.util/flake-id) 53 | :user_id "20000000-81c1-4f1a-aa22-eeb57d2eea98" 54 | :to_id "10000000-3c59-4887-995b-cf275db86343" 55 | :body "Hi tienson" 56 | :created_at (clj-time.coerce/to-date (clj-time.core/now))}})) 57 | -------------------------------------------------------------------------------- /src/lymchat/shared/component/language.cljs: -------------------------------------------------------------------------------- 1 | (ns lymchat.shared.component.language 2 | (:require [reagent.core :as r] 3 | [re-frame.core :refer [subscribe dispatch]] 4 | [lymchat.styles :refer [styles pl-style]] 5 | [lymchat.shared.ui :refer [react-native list-view view text input touchable-opacity colors]] 6 | [lymchat.locales :refer [locales]])) 7 | 8 | (defn row-cp 9 | [row dispatch-key] 10 | [touchable-opacity {:on-press #(dispatch [dispatch-key row])} 11 | [text {:style {:padding-top 15 12 | :padding-bottom 15 13 | :padding-left 10 14 | :font-size 16}} 15 | row]]) 16 | 17 | (defn filter-langs 18 | [pattern langs] 19 | (-> 20 | (if pattern 21 | (-> 22 | (filter #(clojure.string/starts-with? % pattern) langs) 23 | (distinct) 24 | (sort)) 25 | ["Chinese" "Spanish" "English" "Hindi" "Arabic" "Portuguese" "Bengali" "Russian" "Japanese" "Javanese"]))) 26 | 27 | (defn language-cp 28 | [dispatch-key] 29 | (let [current-input (r/atom nil) 30 | ds (.-DataSource (.-ListView react-native)) 31 | list-view-ds (new ds #js {:rowHasChanged #(not= %1 %2)})] 32 | (fn [] 33 | [view {:style (assoc (pl-style :header-container) 34 | :background-color (:white800 colors))} 35 | [view {:style (:search-row styles)} 36 | [input {:style (:search-text-input styles) 37 | :auto-correct false 38 | :clear-button-mode "always" 39 | :on-change-text (fn [value] (reset! current-input value)) 40 | :placeholder "Search..."}] 41 | [list-view {:keyboardShouldPersistTaps true 42 | :enableEmptySections true 43 | :initialListSize 20 44 | :automaticallyAdjustContentInsets false 45 | :dataSource (.cloneWithRows list-view-ds (clj->js (filter-langs @current-input (vals locales)))) 46 | :renderRow (fn [row] (r/as-element (row-cp row dispatch-key))) 47 | :renderSeparator (fn [section-id row-id] 48 | (r/as-element 49 | [view {:key (str section-id "-" row-id) 50 | :style {:height 0.5 51 | :background-color "#ccc"}}]))}]]])) 52 | ) 53 | -------------------------------------------------------------------------------- /api/project.clj: -------------------------------------------------------------------------------- 1 | (defproject api "0.1.0-SNAPSHOT" 2 | :description "Lymchat api server" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :main api.core 7 | :dependencies [[org.clojure/clojure "1.8.0"] 8 | [aleph "0.4.1"] 9 | [com.taoensso/timbre "4.5.1" 10 | :exclusions [org.clojure/tools.reader]] 11 | [com.taoensso/encore "2.61.0" 12 | :exclusions [org.clojure/tools.reader]] 13 | [com.taoensso/sente "1.9.0-beta2" 14 | :exclusions [org.clojure/tools.reader]] 15 | [org.clojure/java.jdbc "0.6.1"] 16 | [org.postgresql/postgresql "9.4-1201-jdbc41"] 17 | [net.postgis/postgis-jdbc "2.2.0" 18 | :exclusions [org.postgresql/postgresql 19 | postgresql]] 20 | [hikari-cp "1.7.1" 21 | :exclusions [org.clojure/clojure 22 | prismatic/schema]] 23 | 24 | [ring/ring-core "1.4.0" 25 | :exclusions [org.clojure/tools.reader]] 26 | [ring/ring-json "0.4.0"] 27 | [ring/ring-devel "1.4.0"] 28 | 29 | [tiensonqin/fnhouse-hacks "0.1.2-SNAPSHOT" 30 | :exclusions [prismatic/plumbing 31 | prismatic/schema]] 32 | [metosin/ring-swagger "0.22.8"] 33 | [metosin/ring-swagger-ui "2.1.4-0"] 34 | [clj-jwt "0.1.1"] 35 | 36 | [environ-plus "0.2.1"] 37 | [com.taoensso/carmine "2.7.1"] 38 | [com.twitter/twitter-text "1.13.4"] 39 | [hiccup "1.0.5"] 40 | [javax.servlet/servlet-api "2.5"] 41 | [org.clojure/tools.nrepl "0.2.10"] 42 | [org.clojure/tools.cli "0.3.2"] 43 | 44 | [ring-cors "0.1.7"] 45 | [amazonica "0.3.74"] 46 | [com.fasterxml.jackson.core/jackson-core "2.8.1"]] 47 | :profiles {:test {:dependencies [[org.clojure/test.check "0.9.0"]]} 48 | :dev {:dependencies [[org.clojure/test.check "0.9.0"] 49 | [enlive "1.1.6"]]} 50 | :ci {:dependencies [[org.clojure/test.check "0.9.0"]]}} 51 | :jvm-opts ["-Xmx1g" "-Duser.timezone=UTC"]) 52 | -------------------------------------------------------------------------------- /api/src/api/pg/coerce.clj: -------------------------------------------------------------------------------- 1 | (ns api.pg.coerce 2 | (:require [api.pg.spatial :as st])) 3 | 4 | (defmulti geojson->postgis :type) 5 | 6 | (defmethod geojson->postgis :Point 7 | [m] 8 | (apply st/point (:coordinates m))) 9 | 10 | (defmethod geojson->postgis :MultiPoint 11 | [m] 12 | (st/multi-point (:coordinates m))) 13 | 14 | (defmethod geojson->postgis :LineString 15 | [m] 16 | (st/line-string (:coordinates m))) 17 | 18 | (defmethod geojson->postgis :MultiLineString 19 | [m] 20 | (st/multi-line-string (:coordinates m))) 21 | 22 | (defmethod geojson->postgis :Polygon 23 | [m] 24 | (st/polygon (:coordinates m))) 25 | 26 | (defmethod geojson->postgis :MultiPolygon 27 | [m] 28 | (st/multi-polygon (:coordinates m))) 29 | 30 | (defprotocol PostgisToCoords 31 | (postgis->coords [o])) 32 | 33 | (extend-protocol PostgisToCoords 34 | org.postgis.Point 35 | (postgis->coords [o] 36 | (if (= (.dimension o) 3) 37 | [(.x o) (.y o) (.z o)] 38 | [(.x o) (.y o)])) 39 | org.postgis.MultiPoint 40 | (postgis->coords [o] 41 | (mapv postgis->coords (.getPoints o))) 42 | org.postgis.LineString 43 | (postgis->coords [o] 44 | (mapv postgis->coords (.getPoints o))) 45 | org.postgis.MultiLineString 46 | (postgis->coords [o] 47 | (mapv postgis->coords (.getLines o))) 48 | org.postgis.LinearRing 49 | (postgis->coords [o] 50 | (mapv postgis->coords (.getPoints o))) 51 | org.postgis.Polygon 52 | (postgis->coords [o] 53 | (mapv postgis->coords (for [i (range (.numRings o))] (.getRing o i)))) 54 | org.postgis.MultiPolygon 55 | (postgis->coords [o] 56 | (mapv postgis->coords (.getPolygons o)))) 57 | 58 | (defprotocol PostgisToGeoJSON 59 | (postgis->geojson [o])) 60 | 61 | (extend-protocol PostgisToGeoJSON 62 | org.postgis.Point 63 | (postgis->geojson [o] 64 | {:type :Point 65 | :coordinates (postgis->coords o)}) 66 | org.postgis.MultiPoint 67 | (postgis->geojson [o] 68 | {:type :MultiPoint 69 | :coordinates (postgis->coords o)}) 70 | org.postgis.LineString 71 | (postgis->geojson [o] 72 | {:type :LineString 73 | :coordinates (postgis->coords o)}) 74 | org.postgis.MultiLineString 75 | (postgis->geojson [o] 76 | {:type :MultiLineString 77 | :coordinates (postgis->coords o)}) 78 | org.postgis.Polygon 79 | (postgis->geojson [o] 80 | {:type :Polygon 81 | :coordinates (postgis->coords o)}) 82 | org.postgis.MultiPolygon 83 | (postgis->geojson [o] 84 | {:type :MultiPolygon 85 | :coordinates (postgis->coords o)})) 86 | -------------------------------------------------------------------------------- /src/lymchat/locales.cljs: -------------------------------------------------------------------------------- 1 | (ns lymchat.locales) 2 | 3 | ;; generate from https://www.facebook.com/translations/FacebookLocales.xml 4 | ;; locale -> language 5 | (def locales 6 | {:id_ID "Indonesian", :or_IN "Oriya", :sw_KE "Swahili", :da_DK "Danish", :en_US "English", :es_LA "Spanish", :bg_BG "Bulgarian", :zz_TR "Zazaki", :tr_TR "Turkish", :fa_IR "Persian", :nn_NO "Norwegian", :de_DE "German", :nl_BE "Dutch", :qu_PE "Quechua", :ro_RO "Romanian", :la_VA "Latin", :hu_HU "Hungarian", :eu_ES "Basque", :cb_IQ "Sorani Kurdish", :ms_MY "Malay", :pa_IN "Punjabi", :gl_ES "Galician", :as_IN "Assamese", :yi_DE "Yiddish", :it_IT "Italian", :ka_GE "Georgian", :ht_HT "Haitian Creole", :uk_UA "Ukrainian", :sy_SY "Syriac", :ck_US "Cherokee", :es_MX "Spanish", :kn_IN "Kannada", :gu_IN "Gujarati", :fi_FI "Finnish", :be_BY "Belarusian", :pt_PT "Portuguese", :el_GR "Greek", :bn_IN "Bengali", :gx_GR "Classical Greek", :tk_TM "Turkmen", :is_IS "Icelandic", :ko_KR "Korean", :ha_NG "Hausa", :te_IN "Telugu", :wo_SN "Wolof", :es_ES "Spanish", :mn_MN "Mongolian", :jv_ID "Javanese", :fy_NL "Frisian", :lt_LT "Lithuanian", :zh_TW "Chinese", :rw_RW "Kinyarwanda", :en_IN "English", :nd_ZW "Ndebele", :yo_NG "Yoruba", :ne_NP "Nepali", :fr_CA "French", :sr_RS "Serbian", :ml_IN "Malayalam", :zu_ZA "Zulu", :sv_SE "Swedish", :eo_EO "Esperanto", :ny_MW "Chewa", :si_LK "Sinhala", :es_VE "Spanish", :tt_RU "Tatar", :am_ET "Amharic", :ay_BO "Aymara", :az_AZ "Azerbaijani", :sk_SK "Slovak", :km_KH "Khmer", :ur_PK "Urdu", :nb_NO "Norwegian", :ta_IN "Tamil", :sc_IT "Sardinian", :tz_MA "Tamazight", :sa_IN "Sanskrit", :my_MM "Burmese", :fb_LT "Leet Speak", :br_FR "Breton", :lv_LV "Latvian", :ku_TR "Kurdish", :hy_AM "Armenian", :co_FR "Corsican", :sl_SI "Slovenian", :lg_UG "Ganda", :qc_GT "Quiché", :bs_BA "Bosnian", :ru_RU "Russian", :sq_AL "Albanian", :ak_GH "Akan", :kk_KZ "Kazakh", :es_CO "Spanish", :es_CL "Spanish", :he_IL "Hebrew", :se_NO "Northern Sámi", :pl_PL "Polish", :nl_NL "Dutch", :ky_KG "Kyrgyz", :mi_NZ "Māori", :vi_VN "Vietnamese", :li_NL "Limburgish", :lo_LA "Lao", :ln_CD "Lingala", :ga_IE "Irish", :tl_ST "Klingon", :so_SO "Somali", :ig_NG "Igbo", :ca_ES "Catalan", :en_PI "English", :pt_BR "Portuguese", :tl_PH "Filipino", :cs_CZ "Czech", :ja_JP "Japanese", :rm_CH "Romansh", :sz_PL "Silesian", :ff_NG "Fulah", :tg_TJ "Tajik", :af_ZA "Afrikaans", :uz_UZ "Uzbek", :cx_PH "Cebuano", :et_EE "Estonian", :zh_CN "Chinese", :hi_IN "Hindi", :en_UD "English", :hr_HR "Croatian", :mt_MT "Maltese", :sn_ZW "Shona", :ps_AF "Pashto", :zh_HK "Chinese", :ar_AR "Arabic", :fr_FR "French", :ja_KS "Japanese", :mg_MG "Malagasy", :th_TH "Thai", :cy_GB "Welsh", :mr_IN "Marathi", :fo_FO "Faroese", :mk_MK "Macedonian", :en_GB "English", :xh_ZA "Xhosa", :gn_PY "Guarani"}) 7 | -------------------------------------------------------------------------------- /api/src/api/services/onesignal.clj: -------------------------------------------------------------------------------- 1 | (ns api.services.onesignal 2 | (:require [aleph.http :as http] 3 | [manifold.deferred :as d] 4 | [byte-streams :as bs] 5 | [cheshire.core :refer [generate-string parse-string]] 6 | [environ-plus.core :refer [env]] 7 | [clojure.string :as s] 8 | [api.util :refer [production?]] 9 | [taoensso.timbre :as t])) 10 | 11 | (defonce api-host "https://onesignal.com/api/v1/") 12 | 13 | (defn post 14 | [url body] 15 | (http/post url 16 | {:headers {"content-type" "application/json" 17 | "accpet" "application/json"} 18 | :body (generate-string body)})) 19 | 20 | (defn put 21 | [url body] 22 | (http/put url 23 | {:headers {"content-type" "application/json" 24 | "accpet" "application/json"} 25 | :body (generate-string body)})) 26 | 27 | (defn add-device 28 | "If success, return the player id." 29 | [device-token] 30 | (let [device (cond-> {:app_id (get-in env [:onesignal :app-id]) 31 | :identifier (s/replace device-token #"[^a-zA-Z0-9]" "") 32 | :device_type 0} 33 | (not (production?)) 34 | (assoc :test_type 1))] 35 | (try 36 | @(d/chain 37 | (post (str api-host "players") device) 38 | :body 39 | bs/to-string 40 | parse-string 41 | (fn [result] 42 | (get result "id"))) 43 | (catch clojure.lang.ExceptionInfo e 44 | (t/error e (bs/to-string (:body (ex-data e)))))))) 45 | 46 | (defn send-notification 47 | [player-id message data] 48 | (let [notification (cond-> {:app_id (get-in env [:onesignal :app-id]) 49 | :contents {:en message} 50 | ;; :buttons [] 51 | :isIos true 52 | :include_player_ids [player-id] 53 | :ios_badgeType "Increase" 54 | :ios_badgeCount 1} 55 | (some? data) 56 | (assoc :data data))] 57 | (try 58 | (d/chain 59 | (post (str api-host "notifications") notification) 60 | :body 61 | bs/to-string 62 | (fn [res] 63 | (parse-string res true))) 64 | (catch clojure.lang.ExceptionInfo e 65 | (t/error e (bs/to-string (:body (ex-data e)))))))) 66 | 67 | (defn reset-badge 68 | [player-id] 69 | (try 70 | (put (str api-host "players/" player-id) {:badge_count 0}) 71 | (catch clojure.lang.ExceptionInfo e 72 | (t/error e (bs/to-string (:body (ex-data e))))))) 73 | -------------------------------------------------------------------------------- /api/src/api/handler/admin.clj: -------------------------------------------------------------------------------- 1 | (ns api.handler.admin 2 | (:require [api.schemas :refer :all] 3 | [schema.core :as s] 4 | [plumbing.core :refer :all] 5 | [environ-plus.core :refer [env]] 6 | [api.db 7 | [user :as user] 8 | [report :as report] 9 | [util :refer [default-db]]] 10 | [api.util :refer [flake-id]] 11 | [api.handler.ws :refer [chsk-send!]] 12 | [api.db.notification :as notification] 13 | [clojure.java.jdbc :as j] 14 | [clj-time.core :as t] 15 | [clj-time.coerce :as tc])) 16 | 17 | (defnk users$GET 18 | "Get users." 19 | {:responses {200 s/Any 20 | 400 Wrong 21 | 404 NotFound}} 22 | [[:request 23 | [:query-params {limit :- s/Int 200}]]] 24 | (j/with-db-connection [db default-db] 25 | {:body (user/get-users db limit)})) 26 | 27 | ;; block user 28 | (defnk $users$:id$block$PATCH 29 | "Block user" 30 | {:responses {200 Ack 31 | 400 Wrong 32 | 401 Unauthorized 33 | 404 Missing}} 34 | [[:request 35 | [:uri-args id :- ID]]] 36 | (j/with-db-transaction [db default-db] 37 | (user/block db id) 38 | (report/block db :user id) 39 | ;; send notification 40 | (let [msg {:id (flake-id) 41 | :user_id "10000000-3c59-4887-995b-cf275db86343" 42 | :to_id (str id) 43 | :body "Please update your profile picture, then your friends understand that you are really a human. If you don't want to be matched, just toggle don't match me. Hope you agree with this." 44 | :created_at (tc/to-date (t/now))}] 45 | 46 | (chsk-send! (str id) 47 | [:chat/new-message msg]) 48 | 49 | (notification/send-notification (:redis env) (str id) 50 | "Lym: Please update your profile picture" 51 | {:type "new-message" 52 | :message msg}))) 53 | ack) 54 | 55 | (defnk $users$:id$unblock$PATCH 56 | "Unblock user" 57 | {:responses {200 Ack 58 | 400 Wrong 59 | 401 Unauthorized 60 | 404 Missing}} 61 | [[:request 62 | [:uri-args id :- ID]]] 63 | (j/with-db-transaction [db default-db] 64 | (user/unblock db id) 65 | (report/unblock db :user id)) 66 | ack) 67 | 68 | ;; reports 69 | (defnk reports$GET 70 | "Get reports." 71 | {:responses {200 s/Any 72 | 400 Wrong 73 | 404 NotFound}} 74 | [[:request 75 | [:query-params {limit :- s/Int 200}]]] 76 | (j/with-db-connection [db default-db] 77 | {:body (report/get-reports db limit)})) 78 | 79 | ;; get lym messages 80 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'Lymchat' 2 | 3 | include ':app' 4 | include ':react-native-material-kit' 5 | project(':react-native-material-kit').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-material-kit/android') 6 | include ':react-native-wechat' 7 | project(':react-native-wechat').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-wechat/android') 8 | include ':react-native-webrtc' 9 | project(':react-native-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webrtc/android') 10 | include ':react-native-incall-manager' 11 | project(':react-native-incall-manager').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-incall-manager/android') 12 | include ':react-native-fs' 13 | project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android') 14 | include ':react-native-device-info' 15 | project(':react-native-device-info').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-device-info/android') 16 | include ':react-native-google-signin' 17 | project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-google-signin/android') 18 | include ':react-native-code-push' 19 | project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app') 20 | include ':react-native-image-resizer' 21 | project(':react-native-image-resizer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-resizer/android') 22 | include ':react-native-image-picker' 23 | project(':react-native-image-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-picker/android') 24 | include ':react-native-push-notification' 25 | project(':react-native-push-notification').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-push-notification/android') 26 | include ':realm' 27 | project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android') 28 | include ':react-native-linear-gradient' 29 | project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android') 30 | include ':react-native-vector-icons' 31 | project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') 32 | include ':react-native-fbsdk' 33 | project(':react-native-fbsdk').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fbsdk/android') 34 | include ':react-native-dialogs' 35 | project(':react-native-dialogs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-dialogs/android') 36 | -------------------------------------------------------------------------------- /ios/Lymchat/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-Small.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-Small@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-Small@3x.png", 19 | "scale" : "3x" 20 | }, 21 | { 22 | "size" : "40x40", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-Small-40@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-Small-40@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "57x57", 35 | "idiom" : "iphone", 36 | "filename" : "Icon.png", 37 | "scale" : "1x" 38 | }, 39 | { 40 | "size" : "57x57", 41 | "idiom" : "iphone", 42 | "filename" : "Icon@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "idiom" : "ipad", 59 | "size" : "29x29", 60 | "scale" : "1x" 61 | }, 62 | { 63 | "idiom" : "ipad", 64 | "size" : "29x29", 65 | "scale" : "2x" 66 | }, 67 | { 68 | "idiom" : "ipad", 69 | "size" : "40x40", 70 | "scale" : "1x" 71 | }, 72 | { 73 | "idiom" : "ipad", 74 | "size" : "40x40", 75 | "scale" : "2x" 76 | }, 77 | { 78 | "idiom" : "ipad", 79 | "size" : "50x50", 80 | "scale" : "1x" 81 | }, 82 | { 83 | "idiom" : "ipad", 84 | "size" : "50x50", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "idiom" : "ipad", 89 | "size" : "72x72", 90 | "scale" : "1x" 91 | }, 92 | { 93 | "idiom" : "ipad", 94 | "size" : "72x72", 95 | "scale" : "2x" 96 | }, 97 | { 98 | "idiom" : "ipad", 99 | "size" : "76x76", 100 | "scale" : "1x" 101 | }, 102 | { 103 | "idiom" : "ipad", 104 | "size" : "76x76", 105 | "scale" : "2x" 106 | }, 107 | { 108 | "idiom" : "ipad", 109 | "size" : "83.5x83.5", 110 | "scale" : "2x" 111 | } 112 | ], 113 | "info" : { 114 | "version" : 1, 115 | "author" : "xcode" 116 | }, 117 | "properties" : { 118 | "pre-rendered" : true 119 | } 120 | } -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Disabling obfuscation is useful if you collect stack traces from production crashes 20 | # (unless you are using a system that supports de-obfuscate the stack traces). 21 | -dontobfuscate 22 | 23 | # React Native 24 | 25 | # Keep our interfaces so they can be used by other ProGuard rules. 26 | # See http://sourceforge.net/p/proguard/bugs/466/ 27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 30 | 31 | # Do not strip any method/class that is annotated with @DoNotStrip 32 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 33 | -keep @com.facebook.common.internal.DoNotStrip class * 34 | -keepclassmembers class * { 35 | @com.facebook.proguard.annotations.DoNotStrip *; 36 | @com.facebook.common.internal.DoNotStrip *; 37 | } 38 | 39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 40 | void set*(***); 41 | *** get*(); 42 | } 43 | 44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 46 | -keepclassmembers,includedescriptorclasses class * { native ; } 47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } 48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } 49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } 50 | 51 | -dontwarn com.facebook.react.** 52 | 53 | # okhttp 54 | 55 | -keepattributes Signature 56 | -keepattributes *Annotation* 57 | -keep class okhttp3.** { *; } 58 | -keep interface okhttp3.** { *; } 59 | -dontwarn okhttp3.** 60 | 61 | # okio 62 | 63 | -keep class sun.misc.Unsafe { *; } 64 | -dontwarn java.nio.file.* 65 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 66 | -dontwarn okio.** 67 | 68 | -keep class com.tencent.mm.sdk.** { 69 | * ; 70 | } 71 | -------------------------------------------------------------------------------- /api/src/api/scripts/channel.clj: -------------------------------------------------------------------------------- 1 | (ns api.scripts.channel 2 | (:require [api.db.channel :as c] 3 | [api.db.util :refer [default-db]])) 4 | 5 | (def seeds (read-string (slurp "resources/seeds/channels.edn"))) 6 | 7 | (defn init! 8 | [] 9 | (let [user-id #uuid "10000000-3c59-4887-995b-cf275db86343" 10 | {:keys [lym languages programming places chinese-places nba-teams football-teams countries others chinese-others colleges chinese-colleges]} seeds] 11 | ;; lym 12 | (prn "lym") 13 | (c/create default-db lym) 14 | 15 | ;; languages 16 | (prn "languages") 17 | (doseq [i languages] 18 | (c/create default-db {:name i 19 | :user_id user-id 20 | :type "language"})) 21 | 22 | ;; places 23 | (prn "places") 24 | (doseq [city places] 25 | (c/create default-db (-> city 26 | (update :picture (fn [src] 27 | (str "https://nomadlist.com" src))) 28 | (merge 29 | {:user_id user-id 30 | :type "place"})))) 31 | 32 | (prn "chinese places") 33 | (doseq [city chinese-places] 34 | (c/create default-db {:user_id user-id 35 | :name city 36 | :type "place" 37 | :locale "chinese"})) 38 | 39 | ;; nba-teams 40 | (prn "nba teams") 41 | (doseq [[name pic] nba-teams] 42 | (c/create default-db {:name name 43 | :picture pic 44 | :user_id user-id 45 | :type "nba"})) 46 | 47 | (doseq [[name pic] football-teams] 48 | (c/create default-db {:name name 49 | :picture pic 50 | :user_id user-id 51 | :type "football"})) 52 | 53 | (doseq [[name pic] countries] 54 | (c/create default-db {:name name 55 | :picture pic 56 | :user_id user-id 57 | :type "country"})) 58 | 59 | (doseq [[name pic] colleges] 60 | (c/create default-db {:name name 61 | :picture pic 62 | :user_id user-id 63 | :type "college"})) 64 | 65 | (doseq [[name pic] chinese-colleges] 66 | (c/create default-db {:name name 67 | :picture pic 68 | :user_id user-id 69 | :type "chinese-college"})) 70 | 71 | (prn "others") 72 | (doseq [i others] 73 | (c/create default-db {:name i 74 | :user_id user-id})) 75 | 76 | (prn "chinese others") 77 | (doseq [i chinese-others] 78 | (c/create default-db {:name i 79 | :user_id user-id 80 | :locale "chinese"})))) 81 | -------------------------------------------------------------------------------- /api/src/api/services/slack.clj: -------------------------------------------------------------------------------- 1 | (ns api.services.slack 2 | (:require [aleph.http :as http] 3 | [cheshire.core :refer [generate-string]] 4 | [api.util :refer [prod-or-stage?]] 5 | [clojure.string :as str] 6 | [taoensso.timbre :as t] 7 | [environ-plus.core :refer [env]])) 8 | 9 | (defn slack-escape [message] 10 | "Escape message according to slack formatting spec." 11 | (str/escape message {\< "<" \> ">" \& "&"})) 12 | 13 | (defn send-msg 14 | ([hook msg] 15 | (send-msg hook msg nil)) 16 | ([hook msg {:keys [specific-user]}] 17 | (when (prod-or-stage?) 18 | (let [body {"text" (slack-escape msg)} 19 | body (if specific-user 20 | (assoc body "channel" (str "@" specific-user)) 21 | body)] 22 | (http/post hook 23 | {:headers {"content-type" "application/json" 24 | "accpet" "application/json"} 25 | :body (generate-string body)}))))) 26 | 27 | (def direct-rules 28 | {:ios ["tienson"] 29 | :android ["tienson"] 30 | :web ["tienson"] 31 | }) 32 | 33 | (def rules {:api-exception {:webhook "" 34 | :notifiers ["tienson"]} 35 | :api-latency {:webhook "" 36 | :notifiers ["tienson"]}}) 37 | 38 | (defn at-prefix 39 | [notifiers msg] 40 | (let [prefix (->> notifiers 41 | (map (partial str "@")) 42 | (str/join " "))] 43 | (str prefix "\n" msg))) 44 | 45 | (defn notify 46 | [channel msg] 47 | (let [msg (at-prefix (get-in rules [channel :notifiers]) msg)] 48 | (send-msg (get-in rules [channel :webhook]) msg))) 49 | 50 | (defn new-api-exception 51 | [msg] 52 | (notify :api-exception msg)) 53 | 54 | (defn notify-latency 55 | [msg] 56 | (notify :api-latency msg)) 57 | 58 | (defn notify-exception 59 | [platform msg] 60 | (when-let [notifiers (get direct-rules platform)] 61 | (doseq [notifier notifiers] 62 | (send-msg (get-in rules [:api-exception :webhook]) msg {:specific-user notifier})))) 63 | 64 | (defn to-string 65 | [& messages] 66 | (let [messages (cons (format "Environment: %s" (name (:environment env))) messages)] 67 | (->> (map 68 | #(if (isa? (class %) Exception) 69 | (str % "\n\n" 70 | (apply str (interpose "\n" (.getStackTrace %)))) 71 | (str %)) 72 | messages) 73 | (interpose "\n") 74 | (apply str)))) 75 | 76 | (defmacro error 77 | "Log errors, then push to slack, 78 | first argument could be throwable." 79 | [& messages] 80 | `(do 81 | (t/error ~@messages) 82 | (new-api-exception (to-string ~@messages)))) 83 | 84 | (defmacro notify-platform 85 | "Notify errors." 86 | [platform & messages] 87 | `(notify-exception ~platform (to-string ~@messages))) 88 | 89 | (defmacro debug 90 | [& messages] 91 | `(t/debug ~@messages)) 92 | 93 | (defmacro info 94 | [& messages] 95 | `(t/info ~@messages)) 96 | -------------------------------------------------------------------------------- /api/src/api/pg/spatial.clj: -------------------------------------------------------------------------------- 1 | (ns api.pg.spatial 2 | ":require [api.pg.spatial :as st]" 3 | (:import [org.postgis Geometry PGgeometryLW PGgeometry LineString LinearRing MultiLineString MultiPoint MultiPolygon Point Polygon])) 4 | 5 | (defn srid 6 | "Returns the set SRID of a geometry object" 7 | [^Geometry geometry] 8 | (.getSrid geometry)) 9 | 10 | (defn with-srid! 11 | "Return the geometry object with SRID set. Alters the object." 12 | [^Geometry geometry srid] 13 | (doto geometry 14 | (.setSrid ^int srid))) 15 | 16 | (defn- pointy-structure? 17 | [x] 18 | (or (instance? Point x) 19 | (and (coll? x) 20 | (>= (count x) 2) 21 | (<= (count x) 3) 22 | (every? number? (map number? x))))) 23 | 24 | (defn point 25 | "Make a 2D or 3D Point." 26 | ([x y] 27 | (Point. x y)) 28 | ([x y z] 29 | (Point. x y z)) 30 | ([coll-or-str] 31 | (cond (instance? Point coll-or-str) coll-or-str 32 | (coll? coll-or-str) (let [x (first coll-or-str) 33 | y (second coll-or-str)] 34 | (if-let [z (nth coll-or-str 2 nil)] 35 | (Point. x y z) 36 | (Point. x y))) 37 | :else (Point. (str coll-or-str))))) 38 | 39 | (defn multi-point 40 | "Make a MultiPoint from collection of Points." 41 | [points] 42 | (cond (instance? MultiPoint points) points 43 | (coll? points) (MultiPoint. (into-array Point (map point points))) 44 | :else (MultiPoint. (str points)))) 45 | 46 | (defn line-string 47 | "Make a LineString from a collection of points." 48 | [points] 49 | (cond (instance? LineString points) points 50 | (coll? points) (LineString. (into-array Point (map point points))) 51 | :else (LineString. (str points)))) 52 | 53 | (defn multi-line-string 54 | "Make a MultiLineString from a collection of LineStrings." 55 | [line-strings] 56 | (cond (instance? MultiLineString line-strings) line-strings 57 | (coll? line-strings) (MultiLineString. (into-array LineString (map line-string line-strings))) 58 | :else (MultiLineString. (str line-strings)))) 59 | 60 | (defn linear-ring 61 | "Used for constructing Polygons from Points." 62 | [points] 63 | (cond (instance? LinearRing points) points 64 | (coll? points) (LinearRing. (into-array Point (map point points))) 65 | :else (LinearRing. (str points)))) 66 | 67 | (defn polygon 68 | "Make a Polygon from a collection of Points." 69 | [linear-rings] 70 | (cond (instance? Polygon linear-rings) linear-rings 71 | (coll? linear-rings) (Polygon. (into-array LinearRing (map linear-ring linear-rings))) 72 | :else (Polygon. (str linear-rings)))) 73 | 74 | (defn multi-polygon 75 | "Make a MultiPolygon from collection of Polygons." 76 | [polygons] 77 | (cond (instance? MultiPolygon polygons) polygons 78 | (coll? polygons) (MultiPolygon. (into-array Polygon (map polygon polygons))) 79 | :else (MultiPolygon. (str polygons)))) 80 | 81 | (defn pg-geom 82 | [geometry] 83 | (PGgeometryLW. geometry)) 84 | -------------------------------------------------------------------------------- /ios/RNGoogleSignin/GoogleSignIn.framework/Headers/GIDAuthentication.h: -------------------------------------------------------------------------------- 1 | /* 2 | * GIDAuthentication.h 3 | * Google Sign-In iOS SDK 4 | * 5 | * Copyright 2014 Google Inc. 6 | * 7 | * Use of this SDK is subject to the Google APIs Terms of Service: 8 | * https://developers.google.com/terms/ 9 | */ 10 | 11 | #import 12 | 13 | @protocol GTMFetcherAuthorizationProtocol; 14 | @class GIDAuthentication; 15 | 16 | // @relates GIDAuthentication 17 | // 18 | // The callback block that takes a GIDAuthentication, or an error if attempt to refresh was 19 | // unsuccessful. 20 | typedef void (^GIDAuthenticationHandler)(GIDAuthentication *authentication, NSError *error); 21 | 22 | // @relates GIDAuthentication 23 | // 24 | // The callback block that takes an access token, or an error if attempt to refresh was 25 | // unsuccessful. 26 | typedef void (^GIDAccessTokenHandler)(NSString *accessToken, NSError *error); 27 | 28 | // This class represents the OAuth 2.0 entities needed for sign-in. 29 | @interface GIDAuthentication : NSObject 30 | 31 | // The client ID associated with the authentication. 32 | @property(nonatomic, readonly) NSString *clientID; 33 | 34 | // The OAuth2 access token to access Google services. 35 | @property(nonatomic, readonly) NSString *accessToken; 36 | 37 | // The estimated expiration date of the access token. 38 | @property(nonatomic, readonly) NSDate *accessTokenExpirationDate; 39 | 40 | // The OAuth2 refresh token to exchange for new access tokens. 41 | @property(nonatomic, readonly) NSString *refreshToken; 42 | 43 | // An OpenID Connect ID token that identifies the user. Send this token to your server to 44 | // authenticate the user there. For more information on this topic, see 45 | // https://developers.google.com/identity/sign-in/ios/backend-auth 46 | @property(nonatomic, readonly) NSString *idToken; 47 | 48 | // The estimated expiration date of the ID token. 49 | @property(nonatomic, readonly) NSDate *idTokenExpirationDate; 50 | 51 | // Gets a new authorizer for GTLService, GTMSessionFetcher, or GTMHTTPFetcher. 52 | - (id)fetcherAuthorizer; 53 | 54 | // Get a valid access token and a valid ID token, refreshing them first if they have expired or are 55 | // about to expire. 56 | - (void)getTokensWithHandler:(GIDAuthenticationHandler)handler; 57 | 58 | // Refreshes the access token and the ID token using the refresh token. 59 | - (void)refreshTokensWithHandler:(GIDAuthenticationHandler)handler; 60 | 61 | // Gets the access token, which may be a new one from the refresh token if the original has already 62 | // expired or is about to expire. Deprecated: use |getTokensWithHandler:| to get access tokens 63 | // instead. 64 | - (void)getAccessTokenWithHandler:(GIDAccessTokenHandler)handler 65 | DEPRECATED_MSG_ATTRIBUTE("Use |getTokensWithHandler:| instead."); 66 | 67 | // Refreshes the access token with the refresh token. Deprecated: Use |refreshTokensWithHandler:| 68 | // to refresh access tokens instead. 69 | - (void)refreshAccessTokenWithHandler:(GIDAccessTokenHandler)handler 70 | DEPRECATED_MSG_ATTRIBUTE("Use |refreshTokensWithHandler:| instead."); 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /src/lymchat/shared/scene/member.cljs: -------------------------------------------------------------------------------- 1 | (ns lymchat.shared.scene.member 2 | (:require [reagent.core :as r] 3 | [re-frame.core :refer [subscribe dispatch]] 4 | [lymchat.styles :refer [styles pl-style]] 5 | [lymchat.shared.ui :refer [text view touchable-highlight list-view react-native button icon image input touchable-opacity colors activity-indicator moment refresh-control]] 6 | [lymchat.photo :refer [offline-avatar-cp]] 7 | [lymchat.shared.scene.chat :refer [channel-current-input]] 8 | [lymchat.realm :as realm] 9 | [clojure.string :as str] 10 | [lymchat.util :as util] 11 | [cljs-time.coerce :as tc] 12 | [lymchat.ws :as ws])) 13 | 14 | (defn member-cp 15 | [user] 16 | (let [{:keys [id name username avatar]} (js->clj user :keywordize-keys true)] 17 | [touchable-opacity {:on-press (fn [] 18 | (dispatch [:nav/push {:key :channel-profile 19 | :title "" 20 | :user user}])) 21 | :style {:flex-direction "column"}} 22 | [view {:style {:flex-direction "row" 23 | :padding-left 10 24 | :padding-right 10 25 | :padding-bottom 10 26 | :padding-top 10 27 | :border-width 0.5 28 | :border-color "#ccc"}} 29 | [image {:source {:uri avatar} 30 | :style {:width 40 31 | :height 40 32 | :resizeMode "cover" 33 | :border-radius 4}}] 34 | [view {:style {:margin-left 10}} 35 | [text {:style {:font-weight "500" 36 | :color "rgba(0,0,0,0.8)"}} 37 | name] 38 | 39 | [text {:style {:margin-top 5 40 | :color "rgba(0,0,0,0.6)"}} 41 | (str "@" username)]]]])) 42 | 43 | (defn members-cp 44 | [channel] 45 | (let [members (subscribe [:channel-members (aget channel "id")])] 46 | (when (empty? @members) 47 | (ws/get-members (aget channel "id"))) 48 | (fn [] 49 | (let [ds (.-DataSource (.-ListView react-native)) 50 | list-view-ds (new ds #js {:rowHasChanged #(not= %1 %2)})] 51 | [list-view {:style (assoc (pl-style :header-container) 52 | :background-color (:white800 colors)) 53 | :automaticallyAdjustContentInsets false 54 | :dataSource (.cloneWithRows list-view-ds (clj->js @members)) 55 | :renderRow (fn [row] 56 | (r/as-element (member-cp row))) 57 | :renderSeparator (fn [section-id row-id] 58 | (r/as-element 59 | [view {:key (str section-id "-" row-id) 60 | :style {:height 0.5 61 | :margin-left 10 62 | :margin-right 10 63 | :background-color "#efefef"}}]))}])))) 64 | -------------------------------------------------------------------------------- /api/src/api/fnhouse/swagger.clj: -------------------------------------------------------------------------------- 1 | (ns api.fnhouse.swagger 2 | (:require 3 | [plumbing.core :refer :all] 4 | [ring.swagger.swagger2 :as swagger] 5 | [ring.swagger.ui :as swagger-ui] 6 | [ring.swagger.middleware :refer [comp-mw]] 7 | [clojure.set :refer [map-invert]] 8 | [schema.core :as s])) 9 | 10 | ;; 11 | ;; Internals 12 | ;; 13 | 14 | (defn- convert-parameters [request] 15 | (let [parameters (for-map [[type f] {:body :body, :query :query-params, :path :uri-args} 16 | :let [model (f request)] 17 | :when (and model (not (empty? model)))] 18 | type model)] 19 | (if-not (empty? parameters) 20 | {:parameters parameters}))) 21 | 22 | (defn- convert-responses [responses] 23 | (let [responses (for-map [[code model] responses 24 | :let [message (or (some-> model meta :message) "")]] 25 | code {:description message, :schema model})] 26 | (if-not (empty? responses) 27 | {:responses responses}))) 28 | 29 | (defn- ignore-ns? [ns-sym] 30 | (:no-doc (meta (the-ns ns-sym)))) 31 | 32 | (defn- collect-route [ns-sym->prefix extra-metadata-fn routes annotated-handler] 33 | (letk [[[:info method path description request responses annotations 34 | [:source-map ns]]] annotated-handler] 35 | (let [ns-sym (symbol ns) 36 | prefix (ns-sym->prefix ns-sym)] 37 | (if (ignore-ns? ns-sym) 38 | routes 39 | (assoc-in routes [path method] 40 | (merge (extra-metadata-fn annotations) 41 | (convert-responses responses) 42 | (convert-parameters request) 43 | {:tags [prefix] 44 | :summary description})))))) 45 | 46 | ;; 47 | ;; Public API 48 | ;; 49 | 50 | (defn collect-routes 51 | "Parameters: 52 | - seq of fnhouse AnnotatedProtoHandlers 53 | - prefix->ns-sym map 54 | - top-level extra swagger parameters to be used as a baseline, defaults to {} 55 | - extra function (like fnhouse extra-info-fn). It takes contents of :annotations 56 | field on handler and returns a map that will be merged into 57 | ring-swagger's Operation-data. Such function can be used to obtain Swagger auth 58 | spec from fnhouse's handler or to override any fnhouse-swagger-derived 59 | metadata." 60 | ([handlers prefix->ns-sym] 61 | (collect-routes handlers prefix->ns-sym {})) 62 | ([handlers prefix->ns-sym base] 63 | (collect-routes handlers prefix->ns-sym base (constantly {}))) 64 | ([handlers prefix->ns-sym extra-parameters extra-metadata-fn] 65 | (let [ns-sym->prefix (map-invert prefix->ns-sym) 66 | route-collector (partial collect-route ns-sym->prefix extra-metadata-fn) 67 | routes (reduce route-collector {} handlers)] 68 | (assoc extra-parameters :paths routes)))) 69 | 70 | (def wrap-swagger-ui 71 | (comp-mw swagger-ui/wrap-swagger-ui :swagger-docs "swagger.json")) 72 | 73 | ;; 74 | ;; Swagger 2.0 Endpoint 75 | ;; 76 | 77 | (defnk $swagger.json$GET 78 | "Swagger 2.0 Specs" 79 | {:responses {200 s/Any}} 80 | [[:resources swagger]] 81 | {:body (-> (swagger/swagger-json swagger) 82 | (update-in [:paths] #(into (sorted-map) %)))}) 83 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/lymchat/lymchat/MainApplication.java.example: -------------------------------------------------------------------------------- 1 | package com.lymchat.lymchat; 2 | 3 | import android.app.Application; 4 | 5 | import com.BV.LinearGradient.LinearGradientPackage; 6 | import com.aakashns.reactnativedialogs.ReactNativeDialogsPackage; 7 | import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage; 8 | import com.facebook.CallbackManager; 9 | import com.facebook.FacebookSdk; 10 | import com.facebook.react.ReactApplication; 11 | import com.github.xinthink.rnmk.ReactMaterialKitPackage; 12 | import com.oney.WebRTCModule.WebRTCModulePackage; 13 | import com.facebook.react.ReactNativeHost; 14 | import com.facebook.react.ReactPackage; 15 | import com.facebook.react.shell.MainReactPackage; 16 | import com.facebook.reactnative.androidsdk.FBSDKPackage; 17 | import com.imagepicker.ImagePickerPackage; 18 | import com.learnium.RNDeviceInfo.RNDeviceInfo; 19 | import com.microsoft.codepush.react.CodePush; 20 | import com.rnfs.RNFSPackage; 21 | import com.theweflex.react.WeChatPackage; 22 | import com.zxcpoiu.incallmanager.InCallManagerPackage; 23 | 24 | import java.util.Arrays; 25 | import java.util.List; 26 | 27 | import co.apptailor.googlesignin.RNGoogleSigninPackage; 28 | import fr.bamlab.rnimageresizer.ImageResizerPackage; 29 | import io.realm.react.RealmReactPackage; 30 | 31 | public class MainApplication extends Application implements ReactApplication { 32 | 33 | private static CallbackManager mCallbackManager = CallbackManager.Factory.create(); 34 | 35 | protected static CallbackManager getCallbackManager() { 36 | return mCallbackManager; 37 | } 38 | 39 | @Override 40 | public void onCreate() { 41 | super.onCreate(); 42 | FacebookSdk.sdkInitialize(getApplicationContext()); 43 | } 44 | 45 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 46 | 47 | @Override 48 | protected String getJSBundleFile() { 49 | return CodePush.getJSBundleFile(); 50 | } 51 | 52 | @Override 53 | protected boolean getUseDeveloperSupport() { 54 | return BuildConfig.DEBUG; 55 | } 56 | 57 | /** 58 | * A list of packages used by the app. If the app uses additional views 59 | * or modules besides the default ones, add more packages here. 60 | */ 61 | @Override 62 | protected List getPackages() { 63 | return Arrays.asList( 64 | new MainReactPackage(), 65 | new ReactMaterialKitPackage(), 66 | new WeChatPackage(), 67 | new WebRTCModulePackage(), 68 | new ReactNativeDialogsPackage(), 69 | new RealmReactPackage(), 70 | new FBSDKPackage(mCallbackManager), 71 | new RNFSPackage(), 72 | new RNGoogleSigninPackage(), 73 | new ImagePickerPackage(), 74 | new LinearGradientPackage(), 75 | new ReactNativePushNotificationPackage(), 76 | new RNDeviceInfo(), 77 | new LymReactPackage(), 78 | new InCallManagerPackage(), 79 | new ImageResizerPackage(), 80 | new CodePush("Your-codepush-key", MainApplication.this, BuildConfig.DEBUG) 81 | ); 82 | } 83 | }; 84 | 85 | @Override 86 | public ReactNativeHost getReactNativeHost() { 87 | return mReactNativeHost; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /api/resources/sql/init.sql: -------------------------------------------------------------------------------- 1 | -- Users 2 | CREATE TABLE users ( 3 | id UUID DEFAULT uuid_generate_v4() primary key, 4 | flake_id bigint not null unique, 5 | username text NOT NULL, 6 | name text NOT NULL, 7 | avatar text default null, 8 | oauth_type text not null, 9 | oauth_id text not null, 10 | language text default null, 11 | timezone integer default null, 12 | status text DEFAULT null, 13 | no_invite boolean DEFAULT false, 14 | block boolean DEFAULT false, 15 | channels uuid[] default null, 16 | contacts uuid[] default null, 17 | created_at timestamp with time zone NOT NULL, 18 | last_seen_at timestamp with time zone NOT NULL); 19 | 20 | ALTER TABLE users ADD CONSTRAINT users_oauth_id_key UNIQUE (oauth_id); 21 | ALTER TABLE users ADD CONSTRAINT users_username_key UNIQUE (username); 22 | ALTER TABLE users ADD CONSTRAINT created_at_chk CHECK (EXTRACT(TIMEZONE from created_at) = '0'); 23 | ALTER TABLE users ADD CONSTRAINT last_seen_at_chk CHECK (EXTRACT(TIMEZONE from last_seen_at) = '0'); 24 | CREATE INDEX users_last_seen_at_index ON users(last_seen_at DESC); 25 | CREATE INDEX users_created_at_index ON users(created_at DESC); 26 | 27 | -- Invites 28 | CREATE TABLE invites ( 29 | user_id UUID NOT NULL, 30 | invite_id UUID NOT NULL, 31 | state text NOT NULL default 'pending', 32 | created_at timestamp with time zone NOT NULL 33 | ); 34 | ALTER TABLE invites ADD CONSTRAINT invites_user_id_invite_id_key UNIQUE (user_id, invite_id); 35 | ALTER TABLE invites ADD CONSTRAINT created_at_chk CHECK (EXTRACT(TIMEZONE from created_at) = '0'); 36 | 37 | -- stats 38 | CREATE TABLE stats ( 39 | type text not null, 40 | day date not null, 41 | value bigint not null default 0); 42 | ALTER TABLE stats ADD CONSTRAINT stats_type_day UNIQUE (type,day); 43 | 44 | -- reports 45 | CREATE TABLE reports ( 46 | id UUID DEFAULT uuid_generate_v4() primary key, 47 | user_id UUID NOT NULL, 48 | type text not null, 49 | type_id UUID not null, 50 | title text not null, 51 | picture text default null, 52 | description text default null, 53 | block boolean default false, 54 | data json default null, 55 | created_at timestamp with time zone NOT NULL); 56 | ALTER TABLE reports ADD CONSTRAINT created_at_chk CHECK (EXTRACT(TIMEZONE from created_at) = '0'); 57 | 58 | -- channels 59 | CREATE TABLE channels ( 60 | id UUID DEFAULT uuid_generate_v4() primary key, 61 | name text unique not null, 62 | user_id UUID NOT NULL, 63 | is_private boolean default false, 64 | need_invite boolean default false, 65 | purpose text default null, 66 | members_count integer not null default 0, 67 | type text default null, 68 | picture text default null, 69 | block boolean default false, 70 | locale text default 'english', 71 | created_at timestamp with time zone NOT NULL); 72 | ALTER TABLE channels ADD CONSTRAINT created_at_chk CHECK (EXTRACT(TIMEZONE from created_at) = '0'); 73 | 74 | CREATE TABLE channels_members ( 75 | channel_id UUID not null, 76 | user_id UUID not null, 77 | created_at timestamp with time zone NOT NULL); 78 | ALTER TABLE channels_members ADD CONSTRAINT created_at_chk CHECK (EXTRACT(TIMEZONE from created_at) = '0'); 79 | ALTER TABLE channels_members ADD CONSTRAINT channel_id_user_id UNIQUE (channel_id,user_id); 80 | -------------------------------------------------------------------------------- /api/src/api/pg/geojson.clj: -------------------------------------------------------------------------------- 1 | (ns api.pg.geojson 2 | (:require [schema.core :as s])) 3 | 4 | ;; 5 | ;; GeoJSON as Primatic Schema 6 | ;; 7 | 8 | (s/defschema NamedCRS 9 | {:type (s/eq :name) 10 | :properties {:name s/Str}}) 11 | 12 | (s/defschema LinkedCRS 13 | {:type (s/eq :link) 14 | :properties {:href s/Str 15 | :type s/Str}}) 16 | 17 | (s/defschema CRS 18 | "Coordinate Reference System - default is WGS84 if not defined." 19 | (s/either NamedCRS LinkedCRS)) 20 | 21 | (s/defschema BBox 22 | [s/Num]) 23 | 24 | (s/defschema Position 25 | [s/Num]) 26 | 27 | (s/defschema Point 28 | "GeoJSON Point" 29 | {:type (s/eq :Point) 30 | :coordinates Position 31 | (s/optional-key :crs) CRS 32 | (s/optional-key :bbox) BBox}) 33 | 34 | (s/defschema MultiPoint 35 | "GeoJSON MultiPoint" 36 | {:type (s/eq :MultiPoint) 37 | :coordinates [Position] 38 | (s/optional-key :crs) CRS 39 | (s/optional-key :bbox) BBox}) 40 | 41 | (s/defschema LineStringCoords 42 | [(s/one Position 'p1) (s/one Position 'p2) Position]) 43 | 44 | (s/defschema LineString 45 | "GeoJSON LineString" 46 | {:type (s/eq :LineString) 47 | :coordinates LineStringCoords 48 | (s/optional-key :crs) CRS 49 | (s/optional-key :bbox) BBox}) 50 | 51 | (s/defschema MultiLineString 52 | "GeoJSON MultiLineString" 53 | {:type (s/eq :MultiLineString) 54 | :coordinates [LineStringCoords] 55 | (s/optional-key :crs) CRS 56 | (s/optional-key :bbox) BBox}) 57 | 58 | (s/defschema LinearRingCoords 59 | "LinearRing coordinate array used for building polygons. 60 | The first and last positions must be equivalent." 61 | [(s/one Position 'p1) (s/one Position 'p2) (s/one Position 'p3) (s/one Position 'p4) Position]) 62 | 63 | (s/defschema PolygonCoords 64 | [LinearRingCoords]) 65 | 66 | (s/defschema Polygon 67 | "GeoJSON Polygon" 68 | {:type (s/eq :Polygon) 69 | :coordinates PolygonCoords 70 | (s/optional-key :crs) CRS 71 | (s/optional-key :bbox) BBox}) 72 | 73 | (s/defschema MultiPolygon 74 | "GeoJSON MultiPolygon" 75 | {:type (s/eq :MultiPolygon) 76 | :coordinates [PolygonCoords] 77 | (s/optional-key :crs) CRS 78 | (s/optional-key :bbox) BBox}) 79 | 80 | (declare GeometryCollection) 81 | 82 | (s/defschema Geometry 83 | (s/either Point MultiPoint LineString MultiLineString Polygon MultiPolygon (s/recursive #'GeometryCollection))) 84 | 85 | (s/defschema GeometryCollection 86 | {:type (s/eq :GeometryCollection) 87 | :geometries [Geometry]}) 88 | 89 | (s/defschema Feature 90 | {:geometry (s/maybe Geometry) 91 | :properties (s/maybe {}) 92 | (s/optional-key :id) s/Any}) 93 | 94 | (s/defschema FeatureCollection 95 | {:features [Feature]}) 96 | 97 | ;; 98 | ;; Predicates 99 | ;; 100 | 101 | 102 | (defn schema-pred 103 | [schema m] 104 | (try (s/validate schema m) 105 | true 106 | (catch Exception e 107 | false))) 108 | 109 | (defn point? 110 | [m] 111 | (schema-pred Point m)) 112 | 113 | (defn point 114 | [& coords] 115 | {:post [(s/validate Point %)]} 116 | {:type :Point 117 | :coordinates (into [] coords)}) 118 | 119 | (defn multi-point 120 | [points] 121 | {:pre [(coll? points)] 122 | :post [(s/validate MultiPoint %)]} 123 | {:type :MultiPoint 124 | :coordinates nil}) 125 | -------------------------------------------------------------------------------- /api/src/api/handler/invite.clj: -------------------------------------------------------------------------------- 1 | (ns api.handler.invite 2 | (:require [api 3 | [schemas :refer :all] 4 | [util :refer [doc]]] 5 | [api.db 6 | [invite :as invite] 7 | [util :refer [default-db]]] 8 | [api.services.slack :refer [error]] 9 | [environ-plus.core :refer [env]] 10 | [plumbing.core :refer [defnk]] 11 | [schema.core :as s] 12 | [clojure.java.jdbc :as j] 13 | [api.db.user :as user] 14 | [api.handler.ws :as ws] 15 | [api.db.notification :as notification] 16 | [clj-time.core :as t] 17 | [clj-time.coerce :as tc])) 18 | 19 | (defnk $POST 20 | "Issue a invite request." 21 | {:responses {200 Ack 22 | 401 Unauthorized 23 | 400 Wrong}} 24 | [[:request [:body user_id :- ID invite_id :- ID]]] 25 | (j/with-db-connection [db default-db] 26 | (cond 27 | (true? (:no_invite (user/get db invite_id))) 28 | ack 29 | 30 | (user/item-exists db user_id "contacts" invite_id) 31 | (bad "Already in your contact.") 32 | 33 | :else 34 | (if-let [issue (-> (user/get-bare db user_id) 35 | (update :id str))] 36 | (let [redis (:redis env) 37 | issue-name (:name issue) 38 | data {:type :invite-request 39 | :user issue}] 40 | (invite/create db user_id invite_id) 41 | 42 | ;; ws notification 43 | (ws/send-invitation (str invite_id) issue) 44 | 45 | ;; push notification 46 | (notification/send-notification redis 47 | (str invite_id) 48 | (str issue-name " wants to be your friend.") 49 | data) 50 | ack) 51 | (bad "User not exists."))))) 52 | 53 | (defnk $reply$POST 54 | "Reply a friend request." 55 | {:responses {200 Ack 56 | 401 Unauthorized 57 | 400 Wrong}} 58 | [[:request 59 | [:body 60 | user_id :- ID 61 | issue_id :- ID 62 | reply :- s/Bool]]] 63 | (j/with-db-transaction [db default-db] 64 | (if (user/item-exists db issue_id "contacts" user_id) 65 | (bad "Already in your contact.") 66 | (if (user/exists? db issue_id) 67 | (let [redis (:redis env)] 68 | (if reply 69 | (let [invite (-> (user/get-bare db user_id) 70 | (update :id str)) 71 | data {:type :invite-accept 72 | :user invite}] 73 | (invite/accept db user_id issue_id) 74 | 75 | (ws/send-message {:user_id (str user_id) 76 | :to_id (str issue_id) 77 | :body "I accept your friend invitation, now we can talk." 78 | :data data}) 79 | 80 | ;; push notification 81 | (notification/send-notification redis 82 | (str issue_id) 83 | "I accept your friend invitation, now we can talk." 84 | data)) 85 | 86 | (invite/reject db user_id issue_id)) 87 | ack) 88 | (bad "issue_id not exists."))))) 89 | -------------------------------------------------------------------------------- /ios/Lymchat/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0.3.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleURLTypes 22 | 23 | 24 | CFBundleURLSchemes 25 | 26 | fb207571656306926 27 | 28 | 29 | 30 | CFBundleTypeRole 31 | Editor 32 | CFBundleURLSchemes 33 | 34 | com.googleusercontent.apps.71389590330-vdcl0jr431ujtspng59dnt7t0el7099h 35 | 36 | 37 | 38 | CFBundleTypeRole 39 | Editor 40 | CFBundleURLSchemes 41 | 42 | com.lymchat.lymchat 43 | 44 | 45 | 46 | CFBundleTypeRole 47 | Editor 48 | CFBundleURLSchemes 49 | 50 | wx03afacf52c7da262 51 | 52 | 53 | 54 | CFBundleVersion 55 | 1 56 | CodePushDeploymentKey 57 | $(CODEPUSH_KEY) 58 | FacebookAppID 59 | 207571656306926 60 | FacebookDisplayName 61 | lymchat 62 | ITSAppUsesNonExemptEncryption 63 | 64 | LSApplicationQueriesSchemes 65 | 66 | weixin 67 | wechat 68 | fbapi 69 | fb-messenger-api 70 | fbauth2 71 | fbshareextension 72 | 73 | LSRequiresIPhoneOS 74 | 75 | NSAppTransportSecurity 76 | 77 | NSAllowsArbitraryLoads 78 | 79 | 80 | NSLocationWhenInUseUsageDescription 81 | 82 | UIAppFonts 83 | 84 | Entypo.ttf 85 | EvilIcons.ttf 86 | FontAwesome.ttf 87 | Foundation.ttf 88 | Ionicons.ttf 89 | MaterialIcons.ttf 90 | Octicons.ttf 91 | Zocial.ttf 92 | 93 | UIRequiredDeviceCapabilities 94 | 95 | armv7 96 | 97 | UIStatusBarHidden 98 | 99 | UIStatusBarStyle 100 | UIStatusBarStyleLightContent 101 | UISupportedInterfaceOrientations 102 | 103 | UIInterfaceOrientationPortrait 104 | 105 | UIViewControllerBasedStatusBarAppearance 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /api/swagger-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Swagger UI 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 65 | 66 | 67 | 68 | 78 | 79 |
 
80 |
81 | 82 | 83 | -------------------------------------------------------------------------------- /src/lymchat/notification.cljs: -------------------------------------------------------------------------------- 1 | (ns lymchat.notification 2 | (:require [lymchat.shared.ui :refer [push-notification alert]] 3 | [lymchat.realm :as realm] 4 | [lymchat.util :refer [vibrate]] 5 | [re-frame.core :refer [dispatch]] 6 | [re-frame.db :refer [app-db]] 7 | [lymchat.util :as util])) 8 | 9 | ;; Example 10 | 11 | ;; {:foreground true, 12 | ;; :message "Your newly configured platform settings check out! Click here to finish configuring your app.", 13 | ;; :data {:custom {:i "19641e10-8438-4f95-b2dd-47d6c8679480"}, :remote true}, 14 | ;; :badge nil, 15 | ;; :alert "Your newly configured platform settings check out! Click here to finish configuring your app.", 16 | ;; :sound "default"} 17 | 18 | ;; notifications 19 | ;; [ 20 | ;; new message, open conversation 21 | ;; {:type new-messge} 22 | 23 | ;; {:type invite-accept}] 24 | 25 | ;; {:type friend-invite} 26 | ;; alert 27 | 28 | ;; {:type new-call} 29 | 30 | (defn handler 31 | [{:keys [foreground message data badge alert sound] :as message}] 32 | (let [data (get-in data [:custom :a])] 33 | (cond 34 | ;; new-message 35 | (and 36 | (= "new-message" (:type data)) 37 | (true? foreground)) 38 | 39 | (let [callee (:current-callee @app-db) 40 | from (get-in data [:message :user_id])] 41 | (cond 42 | 43 | (and callee (= callee from)) 44 | nil 45 | 46 | :else 47 | (do 48 | (vibrate) 49 | (when (and callee (not= callee from)) 50 | (dispatch [:set-new-message? true]))))) 51 | 52 | (and 53 | (= "new-message" (:type data)) 54 | (false? foreground)) 55 | 56 | ;; open corresponding conversation 57 | (when (:sync? @app-db) 58 | (dispatch [:new-message-pushed-notification (:message data)])) 59 | 60 | ;; new-channel-message 61 | (and 62 | (= "new-channel-message" (:type data)) 63 | (true? foreground)) 64 | 65 | (let [channel-id (:current-channel @app-db) 66 | msg-channel-id (get-in data [:message :channel_id])] 67 | (cond 68 | 69 | (and channel-id (= channel-id msg-channel-id)) 70 | nil 71 | 72 | :else 73 | (do 74 | (vibrate) 75 | (when (and channel-id (not= channel-id msg-channel-id)) 76 | (dispatch [:set-new-message? true]))))) 77 | 78 | ;; open corresponding channel 79 | (and 80 | (= "new-channel-message" (:type data)) 81 | (false? foreground)) 82 | 83 | (when (:sync? @app-db) 84 | (dispatch [:new-channel-message-pushed-notification (:message data)])) 85 | 86 | ;; new-mention 87 | 88 | ;; jump to mentions 89 | (and 90 | (= "new-mention" (:type data)) 91 | (false? foreground)) 92 | (dispatch [:reset-tab "mentions"]) 93 | 94 | ;; invite-accept 95 | (and 96 | (= "invite-accept" (:type data)) 97 | (false? foreground)) 98 | 99 | (when (:sync? @app-db) 100 | (dispatch [:jump-in-conversation (:user data)])) 101 | 102 | ;; invite-request 103 | (and 104 | (= "invite-request" (:type data)) 105 | (false? foreground)) 106 | 107 | (do 108 | (dispatch [:new-invite (:user data)]) 109 | (dispatch [:nav/push {:key :invitations 110 | :title "Friend request"}])) 111 | 112 | (and 113 | (= "invite-request" (:type data)) 114 | (true? foreground)) 115 | (dispatch [:new-invite (:user data)]) 116 | 117 | :else nil))) 118 | -------------------------------------------------------------------------------- /src/lymchat/shared/scene/login.cljs: -------------------------------------------------------------------------------- 1 | (ns lymchat.shared.scene.login 2 | (:require 3 | [reagent.core :as r] 4 | [re-frame.core :refer [subscribe dispatch]] 5 | [lymchat.styles :refer [styles]] 6 | [lymchat.shared.ui :refer [text view image touchable-highlight card-stack icon-button colors status-bar gradient image-prefetch google-signin dimensions Image gradient] :as ui] 7 | [lymchat.shared.login :as login] 8 | [lymchat.util :as util])) 9 | 10 | (def logo (js/require "./images/logo.png")) 11 | 12 | (defn login-scene 13 | [] 14 | (let [access? (subscribe [:google-access?])] 15 | (r/create-class {:component-will-mount 16 | (fn [] 17 | (util/hide-statusbar)) 18 | 19 | :component-did-mount 20 | (fn [] 21 | (.configure ui/google-signin 22 | (cond 23 | (ui/android?) 24 | #js {:webClientId (.-google_signin_client_id ui/RCTConfig)} 25 | 26 | (ui/ios?) 27 | #js {:iosClientId (.-google_signin_client_id ui/RCTConfig)} 28 | 29 | :else 30 | #js {}))) 31 | 32 | :reagent-render 33 | (fn [] 34 | (let [{:keys [width height]} (js->clj (.get dimensions "window") :keywordize-keys true)] 35 | [view {:style (:login styles)} 36 | [gradient {:style (:gradient styles) 37 | :colors #js [(:teal500 colors) (:teal400 colors) "rgba(255,255,255,0.1)"]}] 38 | [view {:style {:position "absolute" 39 | :top 10 40 | :right 10 41 | :background-color "transparent"}} 42 | [view {:flex 1 43 | :flex-direction "row"} 44 | [image {:style {:width 40 45 | :height 40 46 | :resizeMode "cover"} 47 | :source logo}]]] 48 | 49 | [view 50 | (when-not @access? 51 | [view {:style {:margin-bottom 30}} 52 | [icon-button {:name "wechat" 53 | :width 184.5 54 | :background-color "#3CB034" 55 | :on-press (fn [] 56 | (login/wechat-login))} 57 | "微信登录"]]) 58 | 59 | [view {:style {:margin-bottom 30}} 60 | [icon-button {:name "facebook" 61 | :width 184.5 62 | :background-color "#3b5998" 63 | :on-press (fn [] 64 | (login/fb-login))} 65 | "Sign in with Facebook"]] 66 | 67 | [view {:style {:margin-bottom 30}} 68 | [icon-button {:name "google-plus" 69 | :width 184.5 70 | :background-color "#E54C2C" 71 | :on-press (fn [] 72 | (login/google-login))} 73 | "Sign in with Google"]]]]))}))) 74 | -------------------------------------------------------------------------------- /ios/Lymchat/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /api/src/api/commands/commands.clj: -------------------------------------------------------------------------------- 1 | (ns api.commands.commands 2 | (:require [api.services.s3 :refer [put-image]] 3 | [clojure.java.jdbc :as j] 4 | [api.db.util :refer [default-db]] 5 | [clojure.string :as str] 6 | [api.util :refer [get-avatar]] 7 | [api.db.user :as user] 8 | [api.db.channel :as channel] 9 | [taoensso.carmine :as car] 10 | [api.util :refer [wcar*]] 11 | [environ-plus.core :refer [env]])) 12 | 13 | (defn fb-avatar 14 | ([id] 15 | (fb-avatar id nil nil)) 16 | ([id width height] 17 | (str "http://graph.facebook.com/" id "/picture?" 18 | (if (and width height) 19 | (str "width=" width "&height=" height) 20 | "type=large")))) 21 | 22 | (defn all-avatars->s3 23 | [] 24 | (let [users (j/query default-db ["select * from users"])] 25 | (doseq [{:keys [id flake_id avatar channels] :as user} users] 26 | (when-not (re-find #"cloudfront.net" avatar) 27 | (let [source (put-image (str id) (get-avatar avatar))] 28 | (user/update default-db id {:avatar source}) 29 | ;; update channels 30 | (doseq [channel channels] 31 | (wcar* (:redis env) 32 | (car/zremrangebyscore (str channel/redis-members-key channel) (:flake_id user) (:flake_id user)) 33 | (car/zadd (str channel/redis-members-key channel) (:flake_id user) (assoc user :avatar source))))))))) 34 | 35 | (defn rebuild-members-cache 36 | [] 37 | (j/with-db-connection [db default-db] 38 | (let [users (j/query db ["select * from users"])] 39 | (doseq [{:keys [id username channels] :as user} users] 40 | (doseq [channel channels] 41 | (wcar* (:redis env) 42 | (car/zremrangebyscore (str channel/redis-members-key channel) (:flake_id user) (:flake_id user)) 43 | (car/zadd (str channel/redis-members-key channel) (:flake_id user) user))))))) 44 | 45 | (defn rebuild-channels-members 46 | "Remove channel members cache, reload users." 47 | [] 48 | (j/with-db-connection [db default-db] 49 | (let [channels (j/query db ["select * from channels"])] 50 | (doseq [{:keys [id name members_count] :as channel} channels] 51 | (when (> members_count 0) 52 | (let [members (channel/get-db-members db id)] 53 | (wcar* (:redis env) 54 | (car/del (str channel/redis-members-key channel)) 55 | (doseq [{:keys [user_id]} members] 56 | (when-let [user (first (j/query db ["select * from users where id = ?" user_id]))] 57 | (car/zadd (str channel/redis-members-key id) (:flake_id user) user)))))))))) 58 | 59 | (defn delete-user 60 | [user_id] 61 | (j/with-db-connection [db default-db] 62 | (let [user (first (j/query db ["select * from users where id = ?" user_id]))] 63 | (doseq [channel-id (:channels user)] 64 | (wcar* (:redis env) 65 | (car/zremrangebyscore (str channel/redis-members-key channel-id) (:flake_id user) (:flake_id user)))) 66 | (j/delete! db "users" ["id = ?" user_id])))) 67 | 68 | (defn rebuild-members-count 69 | [] 70 | (j/with-db-connection [db default-db] 71 | (let [channels (->> (j/query db ["select * from channels_members"]) 72 | (group-by :channel_id))] 73 | (doseq [[channel-id members] channels] 74 | (channel/update default-db channel-id {:members_count (count members)}) 75 | (wcar* (:redis env) 76 | (car/del (str channel/redis-members-key channel-id)) 77 | (doseq [{:keys [user_id]} members] 78 | (when-let [user (first (j/query db ["select * from users where id = ?" user_id]))] 79 | (car/zadd (str channel/redis-members-key channel-id) (:flake_id user) user)))))))) 80 | -------------------------------------------------------------------------------- /src/lymchat/db.cljs: -------------------------------------------------------------------------------- 1 | (ns lymchat.db 2 | (:require [schema.core :as s :include-macros true] 3 | [lymchat.realm :as realm])) 4 | 5 | (def NavigationState 6 | {:key s/Keyword 7 | :title s/Str 8 | s/Keyword s/Any}) 9 | 10 | (def NavigationParentState 11 | {:index s/Int 12 | :routes [NavigationState]}) 13 | 14 | ;; schema of app-db 15 | (def schema {:nav NavigationParentState 16 | :current-user s/Any 17 | :current-tab s/Any 18 | :contact-search-input (s/maybe s/Str) 19 | :channels-search-input (s/maybe s/Str) 20 | :channels-search-result s/Any 21 | :username-input (s/maybe s/Str) 22 | :username-set? s/Bool 23 | :current-callee (s/maybe s/Str) 24 | :current-channel (s/maybe s/Str) 25 | :in-call? s/Bool 26 | :local-stream s/Any 27 | :remote-stream s/Any 28 | :header? s/Bool 29 | :signing? s/Bool 30 | :google-access? s/Bool 31 | :drawer s/Any 32 | 33 | :contacts s/Any 34 | 35 | :search-members-result s/Any 36 | :channel-auto-focus s/Bool 37 | :mentions s/Any 38 | 39 | :temp-avatar (s/maybe s/Str) 40 | ;; new message while in conversation 41 | :new-message? s/Bool 42 | :channel-messages s/Any 43 | :channel-members s/Any 44 | :photo-modal? s/Any 45 | :recommend-channels s/Any 46 | 47 | :conversations s/Any 48 | :invites s/Any 49 | :current-messages s/Any 50 | :loading? s/Bool 51 | :uploading? s/Bool 52 | :sync? s/Bool 53 | :open-video-call-modal? s/Bool 54 | :net-state (s/maybe s/Bool) 55 | :no-disturb? s/Bool 56 | :scroll-to-top? s/Bool 57 | :hidden-input s/Any 58 | :guide-step s/Any}) 59 | 60 | 61 | ;; initial state of app-db 62 | (def app-db {:nav {:index 0 63 | :routes [{:key :lymchat 64 | :title "Lymchat"}]} 65 | :current-user (realm/me) 66 | :conversations (realm/get-conversations) 67 | :invites (realm/get-invites) 68 | :current-tab "Lymchat" 69 | :contact-search-input nil 70 | :channels-search-input nil 71 | :username-input nil 72 | :hidden-input nil 73 | :net-state nil 74 | 75 | :open-video-call-modal? false 76 | :loading? false 77 | :uploading? false 78 | :sync? false 79 | :in-call? false 80 | :signing? false 81 | 82 | :no-disturb? (boolean (realm/kv-get "no-disturb?")) 83 | 84 | ;; posts 85 | :scroll-to-top? false 86 | 87 | ;; chat 88 | :current-messages [] 89 | :new-message? false 90 | 91 | :google-access? true 92 | 93 | :current-channel nil 94 | :mentions nil 95 | 96 | ;; video call 97 | :current-callee nil 98 | :local-stream nil 99 | :remote-stream nil 100 | 101 | :channel-messages (realm/get-groups-messages) 102 | :channel-members {} 103 | :channels-search-result nil 104 | :channel-auto-focus false 105 | :recommend-channels [] 106 | 107 | :photo-modal? {} 108 | 109 | :drawer {:open? false 110 | :ref nil} 111 | :contacts (vec (realm/get-contacts)) 112 | 113 | :temp-avatar nil 114 | :header? true 115 | :guide-step (realm/kv-get :guide-step) 116 | :username-set? (boolean (realm/kv-get :username-set?)) 117 | :search-members-result nil}) 118 | -------------------------------------------------------------------------------- /src/lymchat/shared/scene/contact.cljs: -------------------------------------------------------------------------------- 1 | (ns lymchat.shared.scene.contact 2 | (:require [reagent.core :as r] 3 | [re-frame.core :refer [subscribe dispatch]] 4 | [lymchat.styles :refer [styles pl-style]] 5 | [lymchat.shared.ui :refer [text view touchable-highlight list-view react-native button icon image input realm-react-native realm-list-view touchable-opacity material-icon-button] :as ui] 6 | [lymchat.photo :refer [offline-avatar-cp]] 7 | [lymchat.realm :as realm] 8 | [lymchat.util :as util])) 9 | 10 | (defn row-cp 11 | [row] 12 | (let [{:keys [id name avatar]} (js->clj row :keywordize-keys true)] 13 | [touchable-opacity {:style {:flex 1 14 | :padding 10} 15 | :on-press #(do 16 | (util/show-header) 17 | (util/show-statusbar) 18 | (dispatch [:nav/pop]) 19 | (dispatch [:nav/push {:key :conversation 20 | :title name}]) 21 | (dispatch [:load-conversation-messages id])) 22 | :underlay-color "#eee"} 23 | 24 | [view {:key id 25 | :style {:flex-direction "row"}} 26 | [offline-avatar-cp 27 | id 28 | avatar 29 | {:height 40 30 | :width 40 31 | :border-radius 4}] 32 | 33 | [text {:style {:margin-left 10 34 | :font-size 14}} 35 | name]]])) 36 | 37 | (defn filter-contacts 38 | [pattern contacts] 39 | (if (and pattern (not (empty? contacts))) 40 | (filter #(re-find (js/RegExp. pattern "i") (:name %)) contacts) 41 | contacts)) 42 | 43 | (defn contacts-cp 44 | [] 45 | (let [contacts (subscribe [:contacts]) 46 | current-input (subscribe [:contact-search-input]) 47 | ds (.-DataSource (.-ListView react-native)) 48 | list-view-ds (new ds #js {:rowHasChanged #(not= %1 %2)})] 49 | [view (pl-style :header-container) 50 | [view {:style {:flex 1 51 | :background-color (if (ui/ios?) 52 | "rgba(255,255,255,0.8)" 53 | "#efefef")}} 54 | (when (ui/ios?) 55 | [view {:style {:flex-direction "row" 56 | :margin-top -12}} 57 | [material-icon-button {:name "arrow-back" 58 | :on-press (fn [] 59 | (util/show-header) 60 | (util/show-statusbar) 61 | (dispatch [:nav/pop])) 62 | :size 40 63 | :background-color "transparent" 64 | :color "rgba(0,0,0,0.7)"}] 65 | 66 | [input {:style (:no-border-input styles) 67 | :auto-focus true 68 | :auto-correct true 69 | :clear-button-mode "always" 70 | :on-change-text (fn [value] (dispatch [:reset-contact-search-input value])) 71 | :placeholder "Direct Message"}]]) 72 | [list-view {:keyboardShouldPersistTaps true 73 | :enableEmptySections true 74 | :style {:padding-left 5} 75 | :automaticallyAdjustContentInsets false 76 | :dataSource (.cloneWithRows list-view-ds (clj->js (filter-contacts @current-input @contacts))) 77 | :renderRow (fn [row] (r/as-element (row-cp row))) 78 | :renderSeparator (fn [section-id row-id] 79 | (r/as-element 80 | [view {:key (str section-id "-" row-id) 81 | :style {:height 1 82 | :background-color "#ddd"}}]) 83 | )}]]])) 84 | -------------------------------------------------------------------------------- /api/src/api/db/message.clj: -------------------------------------------------------------------------------- 1 | (ns api.db.message 2 | (:require [taoensso.carmine :as car] 3 | [api.util :refer [wcar*]] 4 | [api.db.util :refer [with-now]] 5 | [environ-plus.core :refer [env]] 6 | [api.db.stats :as stats])) 7 | 8 | (defonce ^:private redis-key "messages:") 9 | (defonce ^:private channels-redis-key "channels_messages:") 10 | 11 | (defn- rk 12 | ([user-id] 13 | (rk user-id redis-key)) 14 | ([redis-key user-id] 15 | (str redis-key user-id))) 16 | 17 | ;; channels only save latest 1000 messages 18 | ;; batch save to pg 19 | (defn create 20 | [db {:keys [to_id] :as message}] 21 | (when message 22 | (wcar* db 23 | (stats/inc-point db :message) 24 | (car/zadd (rk redis-key to_id) 25 | (:id message) 26 | (with-now message [:created_at]))))) 27 | 28 | (defn channels-create 29 | [db {:keys [channel_id] :as message}] 30 | (when message 31 | (wcar* db 32 | (stats/inc-point db :message) 33 | (car/zadd (rk channels-redis-key channel_id) 34 | (:id message) 35 | (with-now message [:created_at]))))) 36 | 37 | (defn get-latest-messages 38 | [db user-id latest-message-id] 39 | (wcar* db 40 | (car/zrangebyscore (rk user-id) 41 | (inc latest-message-id) 42 | "+inf"))) 43 | 44 | (defn get-channel-latest-messages 45 | ([db channel-id] 46 | (get-channel-latest-messages db channel-id nil)) 47 | ([db channel-id {:keys [before-id after-id limit] 48 | :or {limit 20}}] 49 | (let [key (rk channels-redis-key channel-id)] 50 | (cond 51 | (and (nil? before-id) (nil? after-id)) 52 | (wcar* db (car/zrevrangebyscore key "+inf" "-inf" "limit" 0 limit)) 53 | 54 | before-id 55 | (wcar* db (car/zrevrangebyscore key before-id "-inf" "limit" 0 limit)) 56 | 57 | after-id 58 | (wcar* db (car/zrangebyscore key after-id "+inf")))))) 59 | 60 | (defn batch-get-channels-latest-messages 61 | [db channels-ids limit] 62 | (when (seq channels-ids) 63 | (let [result (wcar* db 64 | (doseq [channel-id channels-ids] 65 | (let [key (rk channels-redis-key channel-id)] 66 | (car/zrevrangebyscore key "+inf" "-inf" "limit" 0 limit))))] 67 | (zipmap (map str channels-ids) result)))) 68 | 69 | (defn delete-delivered-messages 70 | [db user-id id] 71 | (wcar* db 72 | (car/zremrangebyscore (rk user-id) 73 | "-inf" 74 | id))) 75 | 76 | (comment 77 | (do (require 'seeds.db) 78 | (let [users (:users seeds.db/seeds)] 79 | (def db api.db.util/default-db) 80 | (def id1 (:id (nth users 0))) 81 | (def id2 (:id (nth users 1))) 82 | (def id3 (:id (nth users 2))) 83 | (def id4 (:id (nth users 3))) 84 | 85 | (create (:redis env) {:id (api.util/flake-id) 86 | :user_id "20000000-81c1-4f1a-aa22-eeb57d2eea98" 87 | :to_id "10000000-3c59-4887-995b-cf275db86343" 88 | :body "Good, I like this!" 89 | :created_at (clj-time.coerce/to-date (clj-time.core/now))}) 90 | 91 | (create (:redis env) {:id (api.util/flake-id) 92 | :user_id "10000000-3c59-4887-995b-cf275db86343" 93 | :to_id "20000000-81c1-4f1a-aa22-eeb57d2eea98" 94 | :body "Hi tienson" 95 | :created_at (clj-time.coerce/to-date (clj-time.core/now))}) 96 | 97 | (channels-create (:redis env) {:id (api.util/flake-id) 98 | :user_id "20000000-81c1-4f1a-aa22-eeb57d2eea98" 99 | :channel_id "10000000-3c59-4887-995b-cf275db86343" 100 | :body "Bingo" 101 | :created_at (clj-time.coerce/to-date (clj-time.core/now))})))) 102 | --------------------------------------------------------------------------------