fix webfinger routing, create group listing api, web homepage with groups list

This commit is contained in:
william Murphy 2019-11-17 19:31:58 -06:00
parent 5407d1cfe1
commit 45f25bdc6a
11 changed files with 115 additions and 146 deletions

View file

@ -29,7 +29,11 @@ app.set('port', process.env.PORT || PORT)
app.set('port-https', process.env.PORT_HTTPS || PORT_HTTPS) app.set('port-https', process.env.PORT_HTTPS || PORT_HTTPS)
app.use(morgan('combined')) app.use(morgan('combined'))
app.use(history({ app.use(history({
index: '/web/index.html' index: '/web/index.html',
rewrites: [
// do not redirect webfinger et c.
{ from: /^\/\.well-known\//, to: context => context.request.originalUrl }
]
})) }))
app.use(bodyParser.json({ app.use(bodyParser.json({
type: pub.consts.jsonldTypes type: pub.consts.jsonldTypes

13
package-lock.json generated
View file

@ -2645,8 +2645,7 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@ -3061,8 +3060,7 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -3118,7 +3116,6 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@ -3162,14 +3159,12 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.3", "version": "3.0.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
} }
} }
}, },

View file

@ -1,7 +1,7 @@
{ {
"name": "guppe", "name": "guppe",
"version": "0.0.1", "version": "0.0.1",
"description": "Decentralized social groups with ActivityPub, NodeJS, Express, and Mongodb", "description": "Decentralized social groups with ActivityPub, NodeJS, Express, Vue, and Mongodb",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"body-parser": "^1.18.3", "body-parser": "^1.18.3",

View file

@ -4,6 +4,30 @@ const router = express.Router()
const pub = require('../pub') const pub = require('../pub')
const net = require('../net') const net = require('../net')
// list active groups
router.get('/', net.validators.jsonld, function (req, res) {
const db = req.app.get('db')
db.collection('streams')
.aggregate([
{ $limit: 10000 }, // don't traverse the entire history
{ $match: { type: 'Announce' } },
{ $group: { _id: '$actor', postCount: { $sum: 1 } } },
{ $lookup: { from: 'objects', localField: '_id', foreignField: 'id', as: 'actor' } },
// merge joined actor up
{ $replaceRoot: { newRoot: { $mergeObjects: [{ $arrayElemAt: ['$actor', 0] }, '$$ROOT'] } } },
{ $project: { postCount: 1, preferredUsername: 1 } }
])
.sort({ postCount: -1 })
.limit(Number.parseInt(req.query.n) || 20)
.toArray()
.then(groups => { console.log(JSON.stringify(groups)); return groups })
.then(groups => res.json(groups))
.catch(err => {
console.log(err.message)
return res.status(500).send()
})
})
router.get('/:name', net.validators.jsonld, function (req, res) { router.get('/:name', net.validators.jsonld, function (req, res) {
const name = req.params.name const name = req.params.name
if (!name) { if (!name) {

47
web/package-lock.json generated
View file

@ -3026,8 +3026,7 @@
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
"dev": true, "dev": true
"optional": true
}, },
"coa": { "coa": {
"version": "2.0.2", "version": "2.0.2",
@ -5634,8 +5633,7 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@ -5656,14 +5654,12 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -5678,20 +5674,17 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -5808,8 +5801,7 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -5821,7 +5813,6 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -5836,7 +5827,6 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@ -5844,14 +5834,12 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.3.5", "version": "2.3.5",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -5870,7 +5858,6 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@ -5951,8 +5938,7 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -5964,7 +5950,6 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -6050,8 +6035,7 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -6087,7 +6071,6 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@ -6107,7 +6090,6 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@ -6151,14 +6133,12 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.3", "version": "3.0.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
} }
} }
}, },
@ -10214,8 +10194,7 @@
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz",
"integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=",
"dev": true, "dev": true
"optional": true
}, },
"rx-lite-aggregates": { "rx-lite-aggregates": {
"version": "4.0.8", "version": "4.0.8",

View file

@ -6,11 +6,11 @@
</div> --> </div> -->
<div class="w3-bar w3-black w3-card">&nbsp;</div> <div class="w3-bar w3-black w3-card">&nbsp;</div>
<div class="w3-row"> <div class="w3-row">
<div class="w3-col s0 m2">&nbsp;</div> <div class="w3-col s0 m1 l2">&nbsp;</div>
<div class="w3-col s12 m8 w3-content"> <div class="w3-col s12 m10 l8 w3-content">
<router-view/> <router-view/>
</div> </div>
<div class="w3-col s0 m2">&nbsp;</div> <div class="w3-col s0 m1 l2">&nbsp;</div>
</div> </div>
<footer class="w3-container w3-padding-64 w3-center w3-opacity w3-light-grey w3-xlarge"> <footer class="w3-container w3-padding-64 w3-center w3-opacity w3-light-grey w3-xlarge">
<a href="https://github.com/wmurphyrd/guppe"><i class="fa fa-github w3-hover-opacity"></i></a> <a href="https://github.com/wmurphyrd/guppe"><i class="fa fa-github w3-hover-opacity"></i></a>
@ -23,7 +23,6 @@
font-family: 'Avenir', Helvetica, Arial, sans-serif; font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50; color: #2c3e50;
} }
</style> </style>

View file

@ -1,59 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-mocha" target="_blank" rel="noopener">unit-mocha</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View file

@ -13,19 +13,11 @@ export default new Router({
name: 'home', name: 'home',
component: Home component: Home
}, },
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
},
{ {
path: '/u/:name', path: '/u/:name',
name: 'profile', name: 'profile',
component: Profile, component: Profile,
props: true, props: true
} }
] ]
}) })

View file

@ -1,5 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

View file

@ -1,40 +1,75 @@
<template> <template>
<div class="w3-container"> <div class="w3-container">
<div class="w3-center">
<img alt="Guppe logo" src="/f/guppe.png"> <img alt="Guppe logo" src="/f/guppe.png">
<h1>Guppe Groups</h1> </div>
<h1 class="w3-center">Guppe Groups</h1>
<p> <p>
Guppe brings social groups to the fediverse &mdash; making it easy to connect and meet new Guppe brings social groups to the fediverse &mdash; making it easy to connect and meet new
people based on shared people based on shared
interests without the maniuplation of your attention to maximize ad revenue nor the interests without the maniuplation of your attention to maximize ad revenue nor the
walled garden lock-in of capitalist social media. walled garden lock-in of capitalist social media.
</p>
<h2 class="w3-center">How does Guppe work?</h2>
<p>
Guppe groups look like regular users you can interact with using your existing account on any
ActivityPub service, but they automatically share anything you send them with all of their followers.
</p> </p>
<h2>How does Guppe work?</h2>
<ol> <ol>
<li>Follow a user on @gup.pe to join that group</li> <li>Follow a group on @gup.pe to join that group</li>
<li>Mention a user on @gup.pe to share a post with everyone in the group</li> <li>Mention a group on @gup.pe to share a post with everyone in the group</li>
<li>New groups are created on demand, just search for @YourGroupNameHere@gup.pe and it will show up</li> <li>New groups are created on demand, just search for or mention @YourGroupNameHere@gup.pe and it will show up</li>
<li>Visit a @gup.pe user profile to see the group history</li> <li>Visit a @gup.pe group profile to see the group history</li>
</ol> </ol>
<p> <h2 class="w3-center">Active Groups</h2>
A Guppe Group has its own account and profile. <div class="profile-grid w3-margin-bottom w3-mobile">
This server-2-server ActivityPub implementation adds decentralized, <div v-for="group of groups" class="w3-card" :key="group._id">
federaded "groups" support across all ActivityPub compliant social media networks. <Profile :name="group.preferredUsername" :post-limit="3"
Users join groups by following group-type actors on Guppe servers and contribute t class="profile"/>
groups by mentioning those same actors in a post. Guppe group actors will <router-link :to="`/u/${group.preferredUsername}`">More...</router-link>
automatically forward posts they receive to all group members so that everyone in the </div>
group sees any post made to the group. Guppe group actors' profiles (e.g. outboxes) also </div>
serve as a group discussion history.
</p>
</div> </div>
</template> </template>
<script> <script>
// @ is an alias to /src import Profile from '@/views/Profile.vue'
// import HelloWorld from '@/components/HelloWorld.vue'
export default { export default {
name: 'home', name: 'home',
components: { components: {
Profile
},
data() {
return {
groups: [],
error: null
}
},
created () {
window.fetch(`/u/`, {
method: 'get',
headers: {
accept: 'application/json'
}
}).then(res => res.json())
.then(json => {
this.groups = json
})
.catch(err => this.error = err.message)
} }
} }
</script> </script>
<style scoped>
.profile {
width: 300px;
}
.profile-grid {
display: grid;
grid-gap: 15px;
grid-template-columns: auto auto;
justify-content: space-evenly;
}
</style>

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="w3-container w3-content w3-center w3-padding-32"> <div class="w3-container w3-content w3-center w3-padding-32">
<img class="profile-main" :src="groupProfileSrc"> <img class="w3-image" :src="groupProfileSrc">
<h2 class="w3-wide">{{ actor.preferredUsername }}</h2> <h2 class="w3-wide">{{ actor.preferredUsername }}</h2>
<p class="w3-opacity"><i>{{ actor.summary }}</i></p> <p class="w3-opacity"><i>{{ actor.summary }}</i></p>
<p class="w3-left-align">To join {{ actor.preferredUsername }}, enter your handle below and you'll be <p class="w3-left-align">To join {{ actor.preferredUsername }}, enter your handle below and you'll be
@ -43,6 +43,10 @@ export default {
name: { name: {
type: String, type: String,
required: true, required: true,
},
postLimit: {
type: Number,
default: 20
} }
}, },
data () { data () {
@ -124,6 +128,7 @@ export default {
.then(outbox => { .then(outbox => {
this.stream = outbox.orderedItems // || fetch page this.stream = outbox.orderedItems // || fetch page
.filter(act => act.type === 'Announce') .filter(act => act.type === 'Announce')
.slice(0, this.postLimit)
}) })
.catch(err => this.error = err.message) .catch(err => this.error = err.message)
} }