Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

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

Closed
wants to merge 5 commits into from

2 participants

@thejh

In registry/app.js:

  • allow everyone to set users[ownUsername] of any package
  • allow "PUT /:package" with only _id, _rev and users

In the webinterface:

  • on the package detail page, show "people who use this"

I'll put up a pull request for "npm use " next.

@thejh

Npm pull request: npm/npm#1249

@isaacs
Owner

Merged into master.

@isaacs isaacs closed this
@thejh thejh referenced this pull request in npm/npm
Closed

add "npm use" #1249

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
119 registry/app.js
@@ -987,6 +987,68 @@ ddoc.updates.package = function (doc, req) {
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
|| function (o) { var a = []
@@ -1015,6 +1077,10 @@ ddoc.updates.package = function (doc, req) {
}
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 (doc.url) {
return error(doc.name+" is hosted elsewhere: "+doc.url)
@@ -1058,6 +1124,10 @@ ddoc.updates.package = function (doc, req) {
if (body.description) doc.description = body.description
if (body.author) doc.author = body.author
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
var tag = req.query.tag
@@ -1162,6 +1232,49 @@ ddoc.validate_doc_update = function (newDoc, oldDoc, user, dbCtx) {
|| (typeof a === "object" && typeof a.length === "number") }
var semver = require("semver")
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)
if (isAdmin()) return
@@ -1173,6 +1286,9 @@ ddoc.validate_doc_update = function (newDoc, oldDoc, user, dbCtx) {
// something detected in the _updates/package script.
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 () {
if ( !oldDoc || !oldDoc.maintainers ) return true
if (isAdmin()) return true
@@ -1246,4 +1362,7 @@ ddoc.validate_doc_update = function (newDoc, oldDoc, user, dbCtx) {
for (var i in newDoc.versions) {
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")
}
View
11 www/attachments/layout.css
@@ -172,9 +172,18 @@ div.dependencies {
padding-left:10%;
padding-right:10%;
}
+div.users {
+ width:80%;
+ text-align:left;
+ padding-left:10%;
+ padding-right:10%;
+}
a.dep {
margin-right:10px;
}
+a.user {
+ margin-right:10px;
+}
div#totals {
float:right;
position:absolute;
@@ -308,4 +317,4 @@ div#bottom-liner {
height:18px;
width:100%;
background-color: #EBEBEB;
-}
+}
View
8 www/attachments/site.js
@@ -529,6 +529,14 @@ app.showPackage = function () {
})
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) {
if (resp.rows.length === 0) return;
Something went wrong with that request. Please try again.