feat: add progress endpoint

This commit is contained in:
mrjvs
2026-08-08 22:10:13 +02:00
parent a41fc8c8a0
commit f3ad06c650
9 changed files with 1002 additions and 1 deletions

View File

@@ -30,6 +30,9 @@ export default defineNuxtConfig({
nitro: {
envPrefix: 'PN_WEBSITE_'
},
githubApiToken: '',
stripeSecretKey: '',
grpcHost: '',
grpcApiKey: '',

667
package-lock.json generated
View File

@@ -22,6 +22,8 @@
"feed": "^6.0.0",
"nice-grpc": "^2.1.17",
"nuxt": "^4.5.2",
"octokit": "^5.0.5",
"stripe": "^22.4.0",
"vue": "^3.5.13",
"vue-router": "^5.2.0",
"zod": "^4.4.3"
@@ -3498,6 +3500,574 @@
"vfile": "^6.0.3"
}
},
"node_modules/@octokit/app": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@octokit/app/-/app-16.1.4.tgz",
"integrity": "sha512-g70WONQyGoBgqIJtv4O0MUEfOF1iJpTfFt3qAW7HaiwiJ6k607xg/gBezCa4tuq6BpO6qXn7qvq8v9tjWtgGQg==",
"license": "MIT",
"dependencies": {
"@octokit/auth-app": "^8.3.0",
"@octokit/auth-unauthenticated": "^7.0.4",
"@octokit/core": "^7.0.7",
"@octokit/oauth-app": "^8.0.4",
"@octokit/plugin-paginate-rest": "^15.0.0",
"@octokit/types": "^17.0.0",
"@octokit/webhooks": "^14.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/app/node_modules/@octokit/openapi-types": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
"license": "MIT"
},
"node_modules/@octokit/app/node_modules/@octokit/plugin-paginate-rest": {
"version": "15.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-15.0.0.tgz",
"integrity": "sha512-lw9A9YL5s4VPj+VEx8uMxaxQm2YTgrPDkrLEuZuu18R8TK3oPcXF4K6t52nx6xn1RcogZC9i188sAr/4XYJaQQ==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^17.0.0"
},
"engines": {
"node": ">= 20"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/app/node_modules/@octokit/types": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^28.0.0"
}
},
"node_modules/@octokit/auth-app": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-8.3.0.tgz",
"integrity": "sha512-/UaKmJCsOc5XBZwhnFiGNdLH/FkDF8lYtBn1QlKxtX7IpgaRB/XOXjixFtERAknyUZHxp3oDuoiG0En4VptJSg==",
"license": "MIT",
"dependencies": {
"@octokit/auth-oauth-app": "^9.0.4",
"@octokit/auth-oauth-user": "^6.0.3",
"@octokit/request": "^10.0.13",
"@octokit/request-error": "^7.1.1",
"@octokit/types": "^17.0.0",
"toad-cache": "^3.7.0",
"universal-github-app-jwt": "^2.2.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
"license": "MIT"
},
"node_modules/@octokit/auth-app/node_modules/@octokit/types": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^28.0.0"
}
},
"node_modules/@octokit/auth-oauth-app": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.4.tgz",
"integrity": "sha512-Pe3du5LrC6dlv10b0RbarBlJtIT1Urq8BFoQklK8WmBw8YKnGgrANn7cQmHiA1v8AVihgi7YutV8MncP57I4og==",
"license": "MIT",
"dependencies": {
"@octokit/auth-oauth-device": "^8.0.4",
"@octokit/auth-oauth-user": "^6.0.3",
"@octokit/request": "^10.0.13",
"@octokit/types": "^17.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
"license": "MIT"
},
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^28.0.0"
}
},
"node_modules/@octokit/auth-oauth-device": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.4.tgz",
"integrity": "sha512-M/+34rmkxMvUnnTo/uqNeVRCXJoJ+5N34eNTbL1KMtIyJpedCTsu/iFL1cWdL+w5hQMlYt6xzXoyux2gn0OVnw==",
"license": "MIT",
"dependencies": {
"@octokit/oauth-methods": "^6.0.3",
"@octokit/request": "^10.0.13",
"@octokit/types": "^17.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
"license": "MIT"
},
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^28.0.0"
}
},
"node_modules/@octokit/auth-oauth-user": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.3.tgz",
"integrity": "sha512-t4OKUhrI5britmpRiSzPAz1TJPyUN/Hw1HEE3Hz/uElxcmnf/YCRSKtFRNc5gy4yJFTlXgk34H1lvxiotQJQvQ==",
"license": "MIT",
"dependencies": {
"@octokit/auth-oauth-device": "^8.0.4",
"@octokit/oauth-methods": "^6.0.3",
"@octokit/request": "^10.0.13",
"@octokit/types": "^17.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
"license": "MIT"
},
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^28.0.0"
}
},
"node_modules/@octokit/auth-token": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz",
"integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==",
"license": "MIT",
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/auth-unauthenticated": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-7.0.4.tgz",
"integrity": "sha512-j4zgVdP8C8C53PNsj4LLI+WFtoykaQARwwrIBILTdxwZ2Ljaa9YUxb1yQ7/seGVnmWPdC3OBefX3LLbh28gKtw==",
"license": "MIT",
"dependencies": {
"@octokit/request-error": "^7.1.1",
"@octokit/types": "^17.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/auth-unauthenticated/node_modules/@octokit/openapi-types": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
"license": "MIT"
},
"node_modules/@octokit/auth-unauthenticated/node_modules/@octokit/types": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^28.0.0"
}
},
"node_modules/@octokit/core": {
"version": "7.0.7",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.7.tgz",
"integrity": "sha512-DcB0M3KFgr9ECI328lhBMVsyFT2DnmNucSBTqEN3exyNKUzkkpUSCHmTRcunF41Eou2TIQKW4seewri8ON9bSA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@octokit/auth-token": "^6.0.0",
"@octokit/graphql": "^9.0.4",
"@octokit/request": "^10.0.13",
"@octokit/request-error": "^7.1.1",
"@octokit/types": "^17.0.0",
"before-after-hook": "^4.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/core/node_modules/@octokit/openapi-types": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
"license": "MIT"
},
"node_modules/@octokit/core/node_modules/@octokit/types": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^28.0.0"
}
},
"node_modules/@octokit/endpoint": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.4.tgz",
"integrity": "sha512-f1cOWoHPmxryJFknxbtDdjODWfV8A9tc8Aae6ermXPNgHFZ/x91AtHIz4gicEjL8hkJiip+u21QHJORfBv/qiA==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^17.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
"license": "MIT"
},
"node_modules/@octokit/endpoint/node_modules/@octokit/types": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^28.0.0"
}
},
"node_modules/@octokit/graphql": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.4.tgz",
"integrity": "sha512-5s15CCiY8XXQ+FG+b1YQcl6Z2FA++nwAz/tg2VUrTmnMncP+2nnGUEYANImdnxsA2Fnq+Mbl7hDjUTw7cFAwcg==",
"license": "MIT",
"dependencies": {
"@octokit/request": "^10.0.13",
"@octokit/types": "^17.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
"license": "MIT"
},
"node_modules/@octokit/graphql/node_modules/@octokit/types": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^28.0.0"
}
},
"node_modules/@octokit/oauth-app": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-8.0.4.tgz",
"integrity": "sha512-Ji5JpRwAJcbOJkO4ij0tW6+GrQ8efpICnAca3o5xI8/HUNjqJu3hhG4cCZdXAHMKaOaQTFX0Yl+cpu61Rsx94A==",
"license": "MIT",
"dependencies": {
"@octokit/auth-oauth-app": "^9.0.4",
"@octokit/auth-oauth-user": "^6.0.3",
"@octokit/auth-unauthenticated": "^7.0.4",
"@octokit/core": "^7.0.7",
"@octokit/oauth-authorization-url": "^8.0.0",
"@octokit/oauth-methods": "^6.0.3",
"@types/aws-lambda": "^8.10.83",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/oauth-authorization-url": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz",
"integrity": "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==",
"license": "MIT",
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/oauth-methods": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-6.0.3.tgz",
"integrity": "sha512-t7MBs34Ja16BBZlOSAFslFh4wBMuofD83wVLG9zIuuEGMv/U12XAJ8ESOifgEcC1ptjhNA3sorLA9BCtVfgPuw==",
"license": "MIT",
"dependencies": {
"@octokit/oauth-authorization-url": "^8.0.0",
"@octokit/request": "^10.0.13",
"@octokit/request-error": "^7.1.1",
"@octokit/types": "^17.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
"license": "MIT"
},
"node_modules/@octokit/oauth-methods/node_modules/@octokit/types": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^28.0.0"
}
},
"node_modules/@octokit/openapi-types": {
"version": "27.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz",
"integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==",
"license": "MIT"
},
"node_modules/@octokit/openapi-webhooks-types": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-12.1.0.tgz",
"integrity": "sha512-WiuzhOsiOvb7W3Pvmhf8d2C6qaLHXrWiLBP4nJ/4kydu+wpagV5Fkz9RfQwV2afYzv3PB+3xYgp4mAdNGjDprA==",
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-graphql": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-6.0.0.tgz",
"integrity": "sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==",
"license": "MIT",
"engines": {
"node": ">= 20"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz",
"integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^16.0.0"
},
"engines": {
"node": ">= 20"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz",
"integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^16.0.0"
},
"engines": {
"node": ">= 20"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-retry": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.1.1.tgz",
"integrity": "sha512-VCVvZ/R1+u3WuiBWpNavZ0mY4aaJNAsENrpBP9aLSR2QyOpQgd7DhM5j4AW7z4MQpnJYgwBPf0XqPQoNBRdQwg==",
"license": "MIT",
"dependencies": {
"@octokit/request-error": "^7.1.1",
"@octokit/types": "^17.0.0",
"bottleneck": "^2.15.3"
},
"engines": {
"node": ">= 20"
},
"peerDependencies": {
"@octokit/core": ">=7"
}
},
"node_modules/@octokit/plugin-retry/node_modules/@octokit/openapi-types": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
"license": "MIT"
},
"node_modules/@octokit/plugin-retry/node_modules/@octokit/types": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^28.0.0"
}
},
"node_modules/@octokit/plugin-throttling": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.5.tgz",
"integrity": "sha512-LIdrkrUv+DWbKeg/49rGuFJ3SU0d3hUS+B4MhNZLepBoNUFXms8Ic9edJjrlx+zycqJHjrMRudVpVb/bAXM2Lw==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^17.0.0",
"bottleneck": "^2.15.3"
},
"engines": {
"node": ">= 20"
},
"peerDependencies": {
"@octokit/core": "^7.0.0"
}
},
"node_modules/@octokit/plugin-throttling/node_modules/@octokit/openapi-types": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
"license": "MIT"
},
"node_modules/@octokit/plugin-throttling/node_modules/@octokit/types": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^28.0.0"
}
},
"node_modules/@octokit/request": {
"version": "10.0.13",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.13.tgz",
"integrity": "sha512-v2269YxL9Yf+x3d+gRI63FP0vFQEiWgLyBzxe/Y+0yFDg2B/Tzf5dhh9VNfccVAQnfcfwQWyk/y6Bn7rUXXs7A==",
"license": "MIT",
"dependencies": {
"@octokit/endpoint": "^11.0.3",
"@octokit/request-error": "^7.1.1",
"@octokit/types": "^17.0.0",
"content-type": "^2.0.0",
"json-with-bigint": "^3.5.3",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/request-error": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.1.tgz",
"integrity": "sha512-+eaY7G2VVpSf2pc5Gn1+mph837V/d/TYTJAgWL9Tb0ogGYcpN3IlAVFgjL+Vv93F/sevrxkvsYCedtpLdcFLzA==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^17.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
"license": "MIT"
},
"node_modules/@octokit/request-error/node_modules/@octokit/types": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^28.0.0"
}
},
"node_modules/@octokit/request/node_modules/@octokit/openapi-types": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
"license": "MIT"
},
"node_modules/@octokit/request/node_modules/@octokit/types": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^28.0.0"
}
},
"node_modules/@octokit/types": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz",
"integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^27.0.0"
}
},
"node_modules/@octokit/webhooks": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-14.2.0.tgz",
"integrity": "sha512-da6KbdNCV5sr1/txD896V+6W0iamFWrvVl8cHkBSPT+YlvmT3DwXa4jxZnQc+gnuTEqSWbBeoSZYTayXH9wXcw==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-webhooks-types": "12.1.0",
"@octokit/request-error": "^7.0.0",
"@octokit/webhooks-methods": "^6.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/webhooks-methods": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-6.0.0.tgz",
"integrity": "sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==",
"license": "MIT",
"engines": {
"node": ">= 20"
}
},
"node_modules/@oxc-parser/binding-android-arm-eabi": {
"version": "0.141.0",
"resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.141.0.tgz",
@@ -5664,6 +6234,12 @@
"tslib": "^2.4.0"
}
},
"node_modules/@types/aws-lambda": {
"version": "8.10.162",
"resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.162.tgz",
"integrity": "sha512-Fn658grtLOci1oxi1391vvDWJRKNGWRSqfxRkmN/Iy3c0tQH1USMKEXcPYHLvope+ZgTFocx9FRQJx1muBL6qw==",
"license": "MIT"
},
"node_modules/@types/debug": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
@@ -7483,6 +8059,12 @@
"node": ">=6.0.0"
}
},
"node_modules/before-after-hook": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz",
"integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==",
"license": "Apache-2.0"
},
"node_modules/better-sqlite3": {
"version": "12.11.1",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz",
@@ -7571,6 +8153,12 @@
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"license": "ISC"
},
"node_modules/bottleneck": {
"version": "2.19.5",
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
"integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==",
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "5.0.9",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
@@ -8111,6 +8699,19 @@
"node": "^14.18.0 || >=16.10.0"
}
},
"node_modules/content-type": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
"integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/convert-hrtime": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz",
@@ -12990,6 +13591,12 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"license": "MIT"
},
"node_modules/json-with-bigint": {
"version": "3.5.10",
"resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.10.tgz",
"integrity": "sha512-Vcx+JVNEBts/xfcoCS69sKrOhOk/3TVlvlT+XzUOefVKnnrbYSCKpDCm10pohsJFtsJVYnwa/cXRZ4eElzaM6w==",
"license": "MIT"
},
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -15900,6 +16507,28 @@
"node": ">=12.20.0"
}
},
"node_modules/octokit": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/octokit/-/octokit-5.0.5.tgz",
"integrity": "sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw==",
"license": "MIT",
"dependencies": {
"@octokit/app": "^16.1.2",
"@octokit/core": "^7.0.6",
"@octokit/oauth-app": "^8.0.3",
"@octokit/plugin-paginate-graphql": "^6.0.0",
"@octokit/plugin-paginate-rest": "^14.0.0",
"@octokit/plugin-rest-endpoint-methods": "^17.0.0",
"@octokit/plugin-retry": "^8.0.3",
"@octokit/plugin-throttling": "^11.0.3",
"@octokit/request-error": "^7.0.2",
"@octokit/types": "^16.0.0",
"@octokit/webhooks": "^14.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/ofetch": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz",
@@ -19216,6 +19845,23 @@
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/stripe": {
"version": "22.4.0",
"resolved": "https://registry.npmjs.org/stripe/-/stripe-22.4.0.tgz",
"integrity": "sha512-LVJ+tcSYeqOSnXr3i+Kz2tZ7y0crLLdP2uwD/4wccrmEVyn0g/Heo0pF7as7rxS/sOjJzrb1lxgZY0Y0Dx1pSA==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/structured-clone-es": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/structured-clone-es/-/structured-clone-es-2.0.1.tgz",
@@ -19569,6 +20215,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/toad-cache": {
"version": "3.7.4",
"resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.4.tgz",
"integrity": "sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==",
"license": "MIT",
"engines": {
"node": ">=20"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -20162,6 +20817,18 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/universal-github-app-jwt": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz",
"integrity": "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==",
"license": "MIT"
},
"node_modules/universal-user-agent": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz",
"integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==",
"license": "ISC"
},
"node_modules/unplugin": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.3.0.tgz",

View File

@@ -26,6 +26,8 @@
"feed": "^6.0.0",
"nice-grpc": "^2.1.17",
"nuxt": "^4.5.2",
"octokit": "^5.0.5",
"stripe": "^22.4.0",
"vue": "^3.5.13",
"vue-router": "^5.2.0",
"zod": "^4.4.3"

View File

@@ -0,0 +1,53 @@
import type { GetProgress, ProgressItem } from "#shared/api-types"
import { getGithubProjects } from "../utils/getGithubProgress";
import { getStripeDonations } from "../utils/getStripeDonations";
const donationGoalCents = 3000 * 100;
export default defineEventHandler(async (event): Promise<GetProgress> => {
const octokit = useOctokit(event);
const stripe = useStripe(event);
const donationData = await getStripeDonations(stripe);
const { projects } = await getGithubProjects(octokit);
const items: ProgressItem[] = projects.map(v => {
const totalTasks = v.tasks.length;
const completedTasks = v.tasks.filter(v => v.status === 'completed').length;
const percentage = Math.floor(completedTasks / totalTasks * 100);
return {
title: v.title,
githubUrl: v.url,
completion: percentage,
tasks: v.tasks.map(task => ({
status: task.status,
title: task.title,
}))
}
});
const summedCompletion = items.reduce((a, v) => a + v.completion, 0);
const completionPercentage = Math.floor(summedCompletion / items.length * 100);
return {
completion: completionPercentage,
donations: {
currentCents: donationData.totalDonationsCents,
goalCents: donationGoalCents,
},
items: projects.map(v => {
const totalTasks = v.tasks.length;
const completedTasks = v.tasks.filter(v => v.status === 'completed').length;
const percentage = Math.floor(completedTasks / totalTasks * 100);
return {
title: v.title,
githubUrl: v.url,
completion: percentage,
tasks: v.tasks.map(task => ({
status: task.status,
title: task.title,
}))
}
})
};
});

View File

@@ -0,0 +1,172 @@
import { Octokit } from "octokit";
export type GithubProjectTaskStatus = 'completed' | 'inprogress' | 'notstarted';
export type GithubProject = {
title: string;
url: string;
tasks: Array<{
title: string;
status: GithubProjectTaskStatus,
}>
}
export type GithubProjectResponse = {
projects: GithubProject[];
}
const orgName = "PretendoNetwork";
const cacheMaxAgeMs = 60 * 60 * 1000; // 1 hour
let cache: { response: GithubProjectResponse, createdAt: Date } | null = null;
const getProjectsV2GQL = `
query getProjectsV2($orgName: String!, $cursor: String) {
organization(login: $orgName) {
projectsV2(first: 10, after: $cursor) {
nodes {
id
title
repositories(first: 1) {
nodes {
url
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
`;
const getProjectsV2FieldsGQL = `
query getProjectsV2Fields($id: ID!, $cursor: String) {
node(id: $id) {
... on ProjectV2 {
items(first: 10, after: $cursor) {
nodes {
content {
... on DraftIssue {
title
}
... on Issue {
title
}
}
fieldValues(first: 20) {
nodes {
... on ProjectV2ItemFieldSingleSelectValue {
name
field {
... on ProjectV2SingleSelectField {
name
}
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`;
async function getGitHubProjectsV2(octokit: Octokit) {
const projects: Array<{ id: string, title: string, url: string | null }> = [];
const data = await octokit.graphql.paginate(getProjectsV2GQL, {
orgName: orgName
});
for (const node of data.organization.projectsV2.nodes) {
projects.push({
id: node.id,
title: node.title,
url: node.repositories.nodes[0]?.url ?? null,
});
}
return projects;
}
async function getGitHubProjectsV2Fields(octokit: Octokit, id: string) {
const output: Array<{ title: string, column: string }> = [];
const data: any = await octokit.graphql.paginate(getProjectsV2FieldsGQL, {
id: id
});
for (const node of data.node.items.nodes) {
output.push({
title: node.content.title,
column: node.fieldValues.nodes.find((fieldValue: any) => fieldValue.field?.name === 'Status')?.name
});
}
return output;
}
async function getGithubProjectsData(octokit: Octokit): Promise<GithubProjectResponse> {
const projects = await getGitHubProjectsV2(octokit);
const output: GithubProject[] = [];
for (const project of projects) {
if (!project.url) {
continue;
}
const projectOutput: GithubProject = {
title: project.title,
url: project.url,
tasks: [],
};
const fields = await getGitHubProjectsV2Fields(octokit, project.id);
const fieldMap: Record<string, GithubProjectTaskStatus> = {
done: 'completed',
in_progress: 'inprogress',
todo: 'notstarted',
}
for (const field of fields) {
const normalizedStatus = field.column.toLowerCase().replace(' ', '_');
const status = fieldMap[normalizedStatus];
if (!status) continue;
projectOutput.tasks.push({
status,
title: field.title,
})
}
output.push(projectOutput);
}
return {
projects: output,
};
}
export async function getGithubProjects(octokit: Octokit | null, ignoreCache = false): Promise<GithubProjectResponse> {
if (!cache || new Date(cache.createdAt.getTime() + cacheMaxAgeMs) < new Date() || ignoreCache) {
// No github credentials, assume there are no projects
if (!octokit) {
return {
projects: [],
}
}
cache = {
createdAt: new Date(),
response: await getGithubProjectsData(octokit)
};
}
return cache.response;
}

View File

@@ -0,0 +1,59 @@
import Stripe from "stripe";
export type StripeDonationResponse = {
donatorCount: number;
totalDonationsCents: number;
}
const cacheMaxAgeMs = 60 * 60 * 1000; // 1 hour
let cache: { response: StripeDonationResponse, createdAt: Date } | null = null;
async function getStripeDonationData(stripe: Stripe): Promise<StripeDonationResponse> {
const donationData: StripeDonationResponse = {
donatorCount: 0,
totalDonationsCents: 0,
};
let hasMore: boolean;
let lastId: string | null = null;
do {
const { data: activeSubscriptions, has_more } = await stripe.subscriptions.list({
limit: 100,
status: 'active',
starting_after: lastId ?? undefined,
});
for (const subscription of activeSubscriptions) {
const plan = (subscription as Stripe.Subscription & { plan?: Stripe.Plan }).plan;
donationData.donatorCount += 1;
donationData.totalDonationsCents += plan?.amount ?? 0;
lastId = subscription.id;
}
hasMore = has_more;
} while (hasMore);
return donationData;
}
export async function getStripeDonations(stripe: Stripe | null, ignoreCache = false): Promise<StripeDonationResponse> {
if (!cache || new Date(cache.createdAt.getTime() + cacheMaxAgeMs) < new Date() || ignoreCache) {
// No credentials, fill in blank data
if (!stripe) {
return {
donatorCount: 0,
totalDonationsCents: 0,
}
}
cache = {
createdAt: new Date(),
response: await getStripeDonationData(stripe)
};
}
return cache.response;
}

13
server/utils/octokit.ts Normal file
View File

@@ -0,0 +1,13 @@
import { Octokit } from "octokit"
let octokit: Octokit | null = null;
export function useOctokit(event: H3Event): Octokit | null {
if (!octokit) {
const config = useRuntimeConfig(event);
if (config.githubApiToken) {
octokit = new Octokit({ auth: config.githubApiToken });
}
}
return octokit;
}

13
server/utils/stripe.ts Normal file
View File

@@ -0,0 +1,13 @@
import { Stripe } from "stripe"
let stripe: Stripe | null = null;
export function useStripe(event: H3Event): Stripe | null {
if (!stripe) {
const config = useRuntimeConfig(event);
if (config.stripeSecretKey) {
stripe = new Stripe(config.stripeSecretKey);
}
}
return stripe;
}

View File

@@ -1,6 +1,6 @@
import { z } from 'zod';
export interface GetApiAuthMe {
export type GetApiAuthMe = {
pid: number;
username: string;
mii: {
@@ -9,6 +9,25 @@ export interface GetApiAuthMe {
} | null;
}
export type ProgressItem = {
title: string,
githubUrl?: string,
completion: number,
tasks: Array<{
status: 'completed' | 'inprogress' | 'notstarted',
title: string,
}>
}
export type GetProgress = {
donations: {
currentCents: number,
goalCents: number,
},
completion: number;
items: ProgressItem[];
}
export type ApiAuthLogin = {
accessToken: string;
refreshToken: string;