2021-11-01 23:05:14 +00:00
require ( 'dotenv' ) . config ( )
2019-09-20 01:55:32 +00:00
const path = require ( 'path' )
2019-09-22 05:20:37 +00:00
const express = require ( 'express' )
const MongoClient = require ( 'mongodb' ) . MongoClient
const fs = require ( 'fs' )
2019-09-20 01:55:32 +00:00
const https = require ( 'https' )
2019-09-28 03:24:35 +00:00
const morgan = require ( 'morgan' )
2019-10-04 02:47:44 +00:00
const history = require ( 'connect-history-api-fallback' )
2021-01-21 04:18:30 +00:00
const { onShutdown } = require ( 'node-graceful-shutdown' )
2020-10-24 17:29:14 +00:00
const ActivitypubExpress = require ( 'activitypub-express' )
2019-09-20 01:55:32 +00:00
2021-11-01 23:05:14 +00:00
const { DOMAIN , KEY _PATH , CERT _PATH , CA _PATH , PORT _HTTPS , DB _URL , DB _NAME } = process . env
2019-09-13 01:03:04 +00:00
2019-09-24 03:18:35 +00:00
const app = express ( )
const client = new MongoClient ( DB _URL , { useUnifiedTopology : true , useNewUrlParser : true } )
2019-09-22 05:20:37 +00:00
const sslOptions = {
2020-10-24 17:29:14 +00:00
key : KEY _PATH && fs . readFileSync ( path . join ( _ _dirname , KEY _PATH ) ) ,
cert : CERT _PATH && fs . readFileSync ( path . join ( _ _dirname , CERT _PATH ) ) ,
ca : CA _PATH && fs . readFileSync ( path . join ( _ _dirname , CA _PATH ) )
2018-09-15 07:01:19 +00:00
}
2020-10-24 17:29:14 +00:00
const icon = {
type : 'Image' ,
mediaType : 'image/jpeg' ,
url : ` https:// ${ DOMAIN } /f/guppe.png `
}
const routes = {
actor : '/u/:actor' ,
object : '/o/:id' ,
activity : '/s/:id' ,
inbox : '/u/:actor/inbox' ,
outbox : '/u/:actor/outbox' ,
followers : '/u/:actor/followers' ,
following : '/u/:actor/following' ,
liked : '/u/:actor/liked' ,
collections : '/u/:actor/c/:id' ,
blocked : '/u/:actor/blocked' ,
rejections : '/u/:actor/rejections' ,
rejected : '/u/:actor/rejected' ,
shares : '/s/:id/shares' ,
likes : '/s/:id/likes'
}
const apex = ActivitypubExpress ( {
domain : DOMAIN ,
actorParam : 'actor' ,
objectParam : 'id' ,
itemsPerPage : 100 ,
routes
} )
2019-12-25 22:22:31 +00:00
app . use ( morgan ( ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status Accepts ":req[accept]" ":referrer" ":user-agent"' ) )
2020-10-24 17:29:14 +00:00
app . use ( express . json ( { type : apex . consts . jsonldTypes } ) , apex )
2021-11-05 03:58:55 +00:00
// Create new groups on demand whenever someone tries to access one
async function actorOnDemand ( req , res , next ) {
const actor = req . params . actor
if ( ! actor ) {
return next ( )
}
2020-10-24 17:29:14 +00:00
const actorIRI = apex . utils . usernameToIRI ( actor )
try {
2021-11-05 03:58:55 +00:00
if ( ! ( await apex . store . getObject ( actorIRI ) ) && actor . length <= 255 ) {
console . log ( ` Creating group: ${ actor } ` )
2020-10-24 17:29:14 +00:00
const summary = ` I'm a group about ${ actor } . Follow me to get all the group posts. Tag me to share with the group. Create other groups by searching for or tagging @yourGroupName@ ${ DOMAIN } `
2021-11-05 03:58:55 +00:00
const actorObj = await apex . createActor ( actor , ` ${ actor } group ` , summary , icon , 'Group' )
2020-10-24 17:29:14 +00:00
await apex . store . saveObject ( actorObj )
2021-11-05 03:58:55 +00:00
}
} catch ( err ) { return next ( err ) }
2020-10-24 17:29:14 +00:00
next ( )
}
// define routes using prepacakged middleware collections
app . route ( routes . inbox )
2021-11-05 03:58:55 +00:00
. post ( actorOnDemand , apex . net . inbox . post )
. get ( actorOnDemand , apex . net . inbox . get )
2020-10-24 17:29:14 +00:00
app . route ( routes . outbox )
2021-11-05 03:58:55 +00:00
. get ( actorOnDemand , apex . net . outbox . get )
2020-10-24 17:29:14 +00:00
// no C2S at present
// .post(apex.net.outbox.post)
// replace apex's target actor validator with our create on demand method
2021-11-05 03:58:55 +00:00
app . get ( routes . actor , actorOnDemand , apex . net . actor . get )
app . get ( routes . followers , actorOnDemand , apex . net . followers . get )
app . get ( routes . following , actorOnDemand , apex . net . following . get )
app . get ( routes . liked , actorOnDemand , apex . net . liked . get )
2020-10-24 17:29:14 +00:00
app . get ( routes . object , apex . net . object . get )
app . get ( routes . activity , apex . net . activityStream . get )
app . get ( routes . shares , apex . net . shares . get )
app . get ( routes . likes , apex . net . likes . get )
app . get (
'/.well-known/webfinger' ,
apex . net . wellKnown . parseWebfinger ,
2021-11-05 03:58:55 +00:00
actorOnDemand ,
apex . net . validators . targetActor ,
2020-10-24 17:29:14 +00:00
apex . net . wellKnown . respondWebfinger
)
app . on ( 'apex-inbox' , async ( { actor , activity , recipient , object } ) => {
switch ( activity . type . toLowerCase ( ) ) {
2021-11-05 03:59:37 +00:00
// automatically reshare incoming posts
2020-10-24 17:29:14 +00:00
case 'create' : {
2021-11-05 04:27:23 +00:00
if ( ! apex . audienceFromActivity ( activity ) . includes ( recipient . id ) ) {
2021-11-05 03:59:37 +00:00
// ignore forwarded messages that aren't directly adddressed to group
2020-10-24 17:29:14 +00:00
return
}
const to = [
recipient . followers [ 0 ] ,
apex . consts . publicAddress
]
const share = await apex . buildActivity ( 'Announce' , recipient . id , to , {
object : activity . id
} )
apex . addToOutbox ( recipient , share )
break
}
2021-11-05 03:59:37 +00:00
// automatically accept follow requests
2020-10-24 17:29:14 +00:00
case 'follow' : {
const accept = await apex . buildActivity ( 'Accept' , recipient . id , actor . id , {
object : activity . id
} )
const { postTask : publishUpdatedFollowers } = await apex . acceptFollow ( recipient , activity )
await apex . addToOutbox ( recipient , accept )
return publishUpdatedFollowers ( )
}
}
} )
/// Guppe web setup
// html/static routes
2019-10-04 02:47:44 +00:00
app . use ( history ( {
2019-11-18 01:31:58 +00:00
index : '/web/index.html' ,
rewrites : [
// do not redirect webfinger et c.
{ from : /^\/\.well-known\// , to : context => context . request . originalUrl }
]
2019-10-04 02:47:44 +00:00
} ) )
2019-09-22 05:20:37 +00:00
app . use ( '/f' , express . static ( 'public/files' ) )
2019-10-04 02:47:44 +00:00
app . use ( '/web' , express . static ( 'web/dist' ) )
2021-11-05 03:59:37 +00:00
// web json routes
app . get ( '/groups' , ( req , res , next ) => {
apex . store . db . collection ( 'streams' )
. aggregate ( [
{ $sort : { _id : - 1 } } , // start from most recent
{ $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 : { _id : 0 , _meta : 0 , actor : 0 } }
] )
. sort ( { postCount : - 1 } )
. limit ( Number . parseInt ( req . query . n ) || 50 )
. toArray ( )
. then ( groups => apex . toJSONLD ( {
id : ` https:// ${ DOMAIN } /groups ` ,
type : 'OrderedCollection' ,
totalItems : groups . length ,
orderedItems : groups
} ) )
// .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 ( )
} )
} )
2018-09-15 07:01:19 +00:00
2019-12-25 21:48:09 +00:00
app . use ( function ( err , req , res , next ) {
console . error ( err . message , req . body , err . stack )
2020-10-24 17:29:14 +00:00
if ( ! res . headersSent ) {
res . status ( 500 ) . send ( 'An error occurred while processing the request' )
}
2019-12-25 21:48:09 +00:00
} )
2019-09-22 05:20:37 +00:00
client . connect ( { useNewUrlParser : true } )
2020-10-24 17:29:14 +00:00
. then ( async ( ) => {
const { default : AutoEncrypt } = await import ( '@small-tech/auto-encrypt' )
apex . store . db = client . db ( DB _NAME )
await apex . store . setup ( )
apex . systemUser = await apex . store . getObject ( apex . utils . usernameToIRI ( 'system_service' ) , true )
if ( ! apex . systemUser ) {
const systemUser = await apex . createActor ( 'system_service' , ` ${ DOMAIN } system service ` , ` ${ DOMAIN } system service ` , icon , 'Service' )
await apex . store . saveObject ( systemUser )
apex . systemUser = systemUser
}
2021-01-21 04:18:30 +00:00
2020-10-24 17:29:14 +00:00
const server = process . env . NODE _ENV === 'production'
? AutoEncrypt . https . createServer ( { domains : [ DOMAIN ] } , app )
: https . createServer ( sslOptions , app )
server . listen ( PORT _HTTPS , function ( ) {
console . log ( 'Guppe server listening on port ' + PORT _HTTPS )
} )
onShutdown ( async ( ) => {
await client . close ( )
await new Promise ( ( resolve , reject ) => {
server . close ( err => ( err ? reject ( err ) : resolve ( ) ) )
} )
console . log ( 'Guppe server closed' )
} )
2021-01-21 04:18:30 +00:00
} )