Skip to content
This repository has been archived by the owner on Aug 11, 2021. It is now read-only.

"I use this" in the database and "people who use this" on search.npmjs.org #27

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions registry/app.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -987,6 +987,68 @@ ddoc.updates.package = function (doc, req) {
return [d.getTime(), d.toUTCString(), ds, ts, tz] return [d.getTime(), d.toUTCString(), ds, ts, tz]
} }
} }

// extend o1 with o2 (in-place)
function deepExtend(o1, o2) {
for (var prop in o2)
if (o2.hasOwnProperty(prop))
if (o1.hasOwnProperty(prop)) {
if (typeof o1[prop] === "object")
deepExtend(o1[prop], o2[prop])
} else
o1[prop] = o2[prop]
return o1
}

// Thank you for helping me with this code, kicken! my version looked so ugly...
// Check whether two objects/arrays are equal while ignoring specific parts.
// The root element must not be an ignored element.
// Ignored parts may be added, altered or removed.
// Usecase: Check whether someone only changed his "I use this" status of a
// package.
// ignoreKeys is an array of paths in the structure that musn't be checked.
// Example:
// var old = {name: "npm", version: "0.0.0", users: { isaacs: true, ryah: true }}
// var new = {name: "npm", version: "0.0.0", users: { isaacs: true, ryah: true, thejh: true }}
// var isAllowedChange = ignoringDeepEquals(old, new, ["users", user.name])
function ignoringDeepEquals(o1, o2, ignoreKeys, pathPrefix){
pathPrefix = pathPrefix || []

function isObject(v){
return typeof v === 'object'
}

// Check whether `arr` contains an array that's shallowly equal to `v`.
function arrayInArray(v, arr) {
return arr.some(function(e) {
if (e.length !== v.length) return false
for (var i=0; i<e.length; i++)
if (e[i] !== v[i])
return false
return true
})
}

function fullPath(p){
return pathPrefix.concat([p])
}

if (typeof o1 !== typeof o2)
return false
else if (!isObject(o1))
return o1 === o2

for (var prop in o1)
if (o1.hasOwnProperty(prop) && !arrayInArray(fullPath(prop), ignoreKeys))
if (!o2.hasOwnProperty(prop) || !ignoringDeepEquals(o1[prop], o2[prop], ignoreKeys, fullPath(prop)))
return false

for (var prop in o2)
if (o2.hasOwnProperty(prop) && !o1.hasOwnProperty(prop) && !arrayInArray(fullPath(prop), ignoreKeys))
return false

return true
}


Object.keys = Object.keys Object.keys = Object.keys
|| function (o) { var a = [] || function (o) { var a = []
Expand Down Expand Up @@ -1015,6 +1077,10 @@ ddoc.updates.package = function (doc, req) {
} }


if (doc) { if (doc) {
var extendedRequest = deepExtend(JSON.parse(req.body), doc)
doc.users = doc.users || {}
if (ignoringDeepEquals(extendedRequest, doc, [["users", req.userCtx.name]]))
return [extendedRequest, JSON.stringify({ok:"updated 'use' status"})]
if (req.query.version) { if (req.query.version) {
if (doc.url) { if (doc.url) {
return error(doc.name+" is hosted elsewhere: "+doc.url) return error(doc.name+" is hosted elsewhere: "+doc.url)
Expand Down Expand Up @@ -1058,6 +1124,10 @@ ddoc.updates.package = function (doc, req) {
if (body.description) doc.description = body.description if (body.description) doc.description = body.description
if (body.author) doc.author = body.author if (body.author) doc.author = body.author
if (body.repository) doc.repository = body.repository if (body.repository) doc.repository = body.repository
if (body.users && body.users[req.userCtx.name] != null) {
if (!doc.users) doc.users = {}
doc.users[req.userCtx.name] = body.users[req.userCtx.name]
}
body.maintainers = doc.maintainers body.maintainers = doc.maintainers


var tag = req.query.tag var tag = req.query.tag
Expand Down Expand Up @@ -1162,6 +1232,49 @@ ddoc.validate_doc_update = function (newDoc, oldDoc, user, dbCtx) {
|| (typeof a === "object" && typeof a.length === "number") } || (typeof a === "object" && typeof a.length === "number") }
var semver = require("semver") var semver = require("semver")
var valid = require("valid") var valid = require("valid")

function ignoringDeepEquals(o1, o2, ignoreKeys, pathPrefix){
pathPrefix = pathPrefix || []

function isObject(v){
return typeof v === 'object'
}

// Check whether `arr` contains an array that's shallowly equal to `v`.
function arrayInArray(v, arr) {
return arr.some(function(e) {
if (e.length !== v.length) return false
for (var i=0; i<e.length; i++)
if (e[i] !== v[i])
return false
return true
})
}

function fullPath(p){
return pathPrefix.concat([p])
}

if (typeof o1 !== typeof o2)
return false
else if (!isObject(o1))
return o1 === o2

for (var prop in o1)
if (o1.hasOwnProperty(prop) && !arrayInArray(fullPath(prop), ignoreKeys))
if (!o2.hasOwnProperty(prop) || !ignoringDeepEquals(o1[prop], o2[prop], ignoreKeys, fullPath(prop)))
return false

for (var prop in o2)
if (o2.hasOwnProperty(prop) && !o1.hasOwnProperty(prop) && !arrayInArray(fullPath(prop), ignoreKeys))
return false

return true
}

if (oldDoc) oldDoc.users = oldDoc.users || {}
newDoc.users = newDoc.users || {}

// admins can do ANYTHING (even break stuff) // admins can do ANYTHING (even break stuff)
if (isAdmin()) return if (isAdmin()) return


Expand All @@ -1173,6 +1286,9 @@ ddoc.validate_doc_update = function (newDoc, oldDoc, user, dbCtx) {
// something detected in the _updates/package script. // something detected in the _updates/package script.
assert(!newDoc.forbidden || newDoc._deleted, newDoc.forbidden) assert(!newDoc.forbidden || newDoc._deleted, newDoc.forbidden)


// everyone may alter his "I use this" status on any package
if (oldDoc && !newDoc._deleted && ignoringDeepEquals(newDoc, oldDoc, [["users", user.name]])) return

function validUser () { function validUser () {
if ( !oldDoc || !oldDoc.maintainers ) return true if ( !oldDoc || !oldDoc.maintainers ) return true
if (isAdmin()) return true if (isAdmin()) return true
Expand Down Expand Up @@ -1246,4 +1362,7 @@ ddoc.validate_doc_update = function (newDoc, oldDoc, user, dbCtx) {
for (var i in newDoc.versions) { for (var i in newDoc.versions) {
assert(semver.valid(i), "version "+i+" is not a valid version") assert(semver.valid(i), "version "+i+" is not a valid version")
} }

assert(ignoringDeepEquals(newDoc.users, (oldDoc || {users:{}}).users, [[user.name]]),
"even the owner of a package may not fake 'I use this' data")
} }
11 changes: 10 additions & 1 deletion www/attachments/layout.css
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -172,9 +172,18 @@ div.dependencies {
padding-left:10%; padding-left:10%;
padding-right:10%; padding-right:10%;
} }
div.users {
width:80%;
text-align:left;
padding-left:10%;
padding-right:10%;
}
a.dep { a.dep {
margin-right:10px; margin-right:10px;
} }
a.user {
margin-right:10px;
}
div#totals { div#totals {
float:right; float:right;
position:absolute; position:absolute;
Expand Down Expand Up @@ -308,4 +317,4 @@ div#bottom-liner {
height:18px; height:18px;
width:100%; width:100%;
background-color: #EBEBEB; background-color: #EBEBEB;
} }
8 changes: 8 additions & 0 deletions www/attachments/site.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -529,6 +529,14 @@ app.showPackage = function () {
}) })
showVersion(this.id) showVersion(this.id)
}) })

var usersStr = '<h4>People who starred '+id+'</h4><div class="users"><p>'
if (doc.users)
for (var usingUser in doc.users)
if (doc.users[usingUser])
usersStr += '<span class="user">'+usingUser.replace(/</g, '&lt;').replace(/>/g, '&gt;')+'</span>'
usersStr += '</p></div>'
package.append(usersStr)


request({url:'/_view/dependencies?reduce=false&key="'+id+'"'}, function (e, resp) { request({url:'/_view/dependencies?reduce=false&key="'+id+'"'}, function (e, resp) {
if (resp.rows.length === 0) return; if (resp.rows.length === 0) return;
Expand Down