Account Admin
Now, it's time to implement our server to support Account Admin access, in this step, we'll continue extend our server based on Account Admin APIs to FETCH and CREATE the data of ACC Projects and Users:
Projects & Users
First, let's add a couple of helper methods for getting and importing projects and users:
- Node.js & VSCode
- .NET & VSCode
- .NET & VS2022
First, let's include the Account Admin SDK, add the @aps_sdk/account-admin
library, and also create an instance of
SDK client of AdminClient
at the beginning of services/aps.js
file:
const { SdkManagerBuilder } = require('@aps_sdk/autodesk-sdkmanager');
const { AuthenticationClient, Scopes, ResponseType } = require('@aps_sdk/authentication');
const { DataManagementClient } = require('@aps_sdk/data-management');
const { AdminClient } = require('@aps_sdk/construction-account-admin');
const { APS_CLIENT_ID, APS_CLIENT_SECRET, APS_CALLBACK_URL } = require('../config.js');
const service = module.exports = {};
const sdk = SdkManagerBuilder.create().build();
const authenticationClient = new AuthenticationClient(sdk);
const dataManagementClient = new DataManagementClient(sdk);
const adminClient = new AdminClient(sdk);
Now append the following code to the end of the services/aps.js
file to support export & import projects and users:
// ACC Admin APIs
service.getProjectsACC = async (accountId, token) => {
let allProjects = [];
let offset = 0;
let totalResults = 0;
do {
const resp = await adminClient.getProjects(accountId, { offset: offset, accessToken: token });
allProjects = allProjects.concat(resp.results);
offset += resp.pagination.limit;
totalResults = resp.pagination.totalResults;
} while (offset < totalResults)
return allProjects;
};
service.createProjectACC = async (accountId, projectInfo, token) =>{
const resp = await adminClient.createProject(accountId, projectInfo, { accessToken: token});
return resp;
}
service.getProjectACC = async (projectId, token) => {
const resp = await adminClient.getProject(projectId, { accessToken: token });
return resp;
};
service.getProjectUsersACC = async (projectId, token) => {
let allUsers = [];
let offset = 0;
let totalResults = 0;
do{
const resp = await adminClient.getProjectUsers(projectId, { offset: offset, accessToken: token });
allUsers = allUsers.concat(resp.results);
offset += resp.pagination.limit;
totalResults = resp.pagination.totalResults;
}while (offset < totalResults)
return allUsers;
};
service.addProjectAdminACC = async (projectId, email, token) => {
const userBody = {
"email": email,
"products": [{
"key": "projectAdministration",
"access": "administrator"
}, {
"key": "docs",
"access": "administrator"
}]
}
const resp = await adminClient.assignProjectUser(projectId, userBody, { accessToken: token });
return resp;
}
service.importProjectUsersACC = async (projectId, projectUsers, token) => {
const resp = await adminClient.importProjectUsers(projectId, projectUsers, { accessToken: token });
return resp;
}
The method adminClient.getProjects() returns all the production projects within this hub, it's different from the Data Management GET Projects API which only returns these projects the current user is involved.
Create a APS.Admin.cs
under the Models subfolder with the following content:
using System.Collections.Generic;
using System.Threading.Tasks;
using Autodesk.Construction.AccountAdmin;
using Autodesk.Construction.AccountAdmin.Model;
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public partial class APS
{
public async Task<IEnumerable<dynamic>> GetProjectsACC(string accountId, Tokens tokens)
{
AdminClient adminClient = new AdminClient(_SDKManager);
List<Project> allProjects = new List<Project>();
var offset = 0;
var totalResult = 0;
do
{
var projects = await adminClient.GetProjectsAsync(accountId,offset:offset,accessToken:tokens.AccessToken);
allProjects.AddRange(projects.Results);
offset += (int)projects.Pagination.Limit;
totalResult = (int)projects.Pagination.TotalResults;
} while (offset < totalResult);
return allProjects;
}
public async Task<dynamic> GetProjectACC(string projectId, Tokens tokens)
{
AdminClient adminClient = new AdminClient(_SDKManager);
var project = await adminClient.GetProjectAsync(projectId, accessToken: tokens.AccessToken);
return project;
}
public async Task<IEnumerable<dynamic>> GetProjectUsersACC(string projectId, Tokens tokens)
{
AdminClient adminClient = new AdminClient(_SDKManager);
var allUsers = new List<ProjectUser>();
var offset = 0;
var totalResult = 0;
do
{
var users = await adminClient.GetProjectUsersAsync(projectId, offset: offset, accessToken: tokens.AccessToken );
allUsers.AddRange(users.Results);
offset += (int)users.Pagination.Limit;
totalResult = (int)users.Pagination.TotalResults;
} while (offset < totalResult);
return allUsers;
}
public async Task<dynamic> CreateProjectACC(string accountId, JObject body, Tokens tokens)
{
AdminClient adminClient = new AdminClient(_SDKManager);
var projectPayload = body.ToObject<ProjectPayload>();
var newProject = await adminClient.CreateProjectAsync(accountId, projectPayload, accessToken: tokens.AccessToken);
return newProject;
}
public async Task<dynamic> AddProjectAdminACC(string projectId, string email, Tokens tokens)
{
AdminClient adminClient = new AdminClient(_SDKManager);
ProjectUserPayload adminUser = new ProjectUserPayload()
{
Email = email,
Products = new List<ProjectUserPayloadProducts>()
{
new ProjectUserPayloadProducts()
{
Key = ProductKeys.ProjectAdministration,
Access = ProductAccess.Administrator
},
new ProjectUserPayloadProducts()
{
Key = ProductKeys.Docs,
Access = ProductAccess.Administrator
}
}
};
var projectUser = await adminClient.AssignProjectUserAsync(projectId, adminUser, accessToken: tokens.AccessToken);
return projectUser;
}
public async Task<dynamic> ImportProjectUsersACC(string projectId, JObject body, Tokens tokens)
{
AdminClient adminClient = new AdminClient(_SDKManager);
var projectUsersPayload = body.ToObject<ProjectUsersImportPayload>();
var usersRes = await adminClient.ImportProjectUsersAsync(projectId, projectUsersPayload, accessToken: tokens.AccessToken);
return usersRes;
}
}
The method adminClient.GetProjectsAsync() returns all the production projects within this hub, it's different from the Data Management GET Projects API which only returns these projects the current user is involved.
Create a APS.Admin.cs
under the Models subfolder with the following content:
using System.Collections.Generic;
using System.Threading.Tasks;
using Autodesk.Construction.AccountAdmin;
using Autodesk.Construction.AccountAdmin.Model;
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public partial class APS
{
public async Task<IEnumerable<dynamic>> GetProjectsACC(string accountId, Tokens tokens)
{
AdminClient adminClient = new AdminClient(_SDKManager);
List<Project> allProjects = new List<Project>();
var offset = 0;
var totalResult = 0;
do
{
var projects = await adminClient.GetProjectsAsync(accountId,offset:offset,accessToken:tokens.AccessToken);
allProjects.AddRange(projects.Results);
offset += (int)projects.Pagination.Limit;
totalResult = (int)projects.Pagination.TotalResults;
} while (offset < totalResult);
return allProjects;
}
public async Task<dynamic> GetProjectACC(string projectId, Tokens tokens)
{
AdminClient adminClient = new AdminClient(_SDKManager);
var project = await adminClient.GetProjectAsync(projectId, accessToken: tokens.AccessToken);
return project;
}
public async Task<IEnumerable<dynamic>> GetProjectUsersACC(string projectId, Tokens tokens)
{
AdminClient adminClient = new AdminClient(_SDKManager);
var allUsers = new List<ProjectUser>();
var offset = 0;
var totalResult = 0;
do
{
var users = await adminClient.GetProjectUsersAsync(projectId, offset: offset, accessToken: tokens.AccessToken );
allUsers.AddRange(users.Results);
offset += (int)users.Pagination.Limit;
totalResult = (int)users.Pagination.TotalResults;
} while (offset < totalResult);
return allUsers;
}
public async Task<dynamic> CreateProjectACC(string accountId, JObject body, Tokens tokens)
{
AdminClient adminClient = new AdminClient(_SDKManager);
var projectPayload = body.ToObject<ProjectPayload>();
var newProject = await adminClient.CreateProjectAsync(accountId, projectPayload, accessToken: tokens.AccessToken);
return newProject;
}
public async Task<dynamic> AddProjectAdminACC(string projectId, string email, Tokens tokens)
{
AdminClient adminClient = new AdminClient(_SDKManager);
ProjectUserPayload adminUser = new ProjectUserPayload()
{
Email = email,
Products = new List<ProjectUserPayloadProducts>()
{
new ProjectUserPayloadProducts()
{
Key = ProductKeys.ProjectAdministration,
Access = ProductAccess.Administrator
},
new ProjectUserPayloadProducts()
{
Key = ProductKeys.Docs,
Access = ProductAccess.Administrator
}
}
};
var projectUser = await adminClient.AssignProjectUserAsync(projectId, adminUser, accessToken: tokens.AccessToken);
return projectUser;
}
public async Task<dynamic> ImportProjectUsersACC(string projectId, JObject body, Tokens tokens)
{
AdminClient adminClient = new AdminClient(_SDKManager);
var projectUsersPayload = body.ToObject<ProjectUsersImportPayload>();
var usersRes = await adminClient.ImportProjectUsersAsync(projectId, projectUsersPayload, accessToken: tokens.AccessToken);
return usersRes;
}
}
The method adminClient.GetProjectsAsync() returns all the production projects within this hub, it's different from the Data Management GET Projects API which only returns these projects the current user is involved.
Server endpoints
Next, let's expose the new functionality to the client-side code through another set of endpoints.
- Node.js & VSCode
- .NET & VSCode
- .NET & VS2022
Create a admin.js
file under the routes
subfolder with the following content:
const express = require('express');
var bodyParser = require('body-parser');
const { authRefreshMiddleware, getProjectsACC, getProjectACC, getProjectUsersACC, createProjectACC, importProjectUsersACC, addProjectAdminACC, getUserProfile } = require('../services/aps.js');
let router = express.Router();
router.use(authRefreshMiddleware);
router.get('/api/admin/projects', async function(req, res, next){
try {
const projects = await getProjectsACC( req.query.accountId, req.oAuthToken.access_token);
res.json(projects);
} catch (err) {
next(err);
}
});
router.get('/api/admin/project', async function(req, res, next){
let projectsList = [];
try {
const projectInfo = await getProjectACC( req.query.projectId, req.oAuthToken.access_token);
projectsList.push(projectInfo);
res.json(projectsList);
} catch (err) {
next(err);
}
});
router.post('/api/admin/projects', bodyParser.json(), async function (req, res, next) {
const accountId = req.body.accountId;
const projects = req.body.data;
let projectsCreated = [];
let projectsFailed = [];
await Promise.all(
projects.map(async (project) => {
try{
let projectInfo = await createProjectACC(accountId, project, req.oAuthToken.access_token);
projectsCreated.push(projectInfo.name);
while( projectInfo.status != "active" ){
function delay(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
await delay(1000);
projectInfo = await getProjectACC( projectInfo.id, req.oAuthToken.access_token);
}
const profile = await getUserProfile(req.oAuthToken);
await addProjectAdminACC( projectInfo.id, profile.email, req.oAuthToken.access_token )
}catch(err){
console.warn("Failed to create project for: "+ project.name + " due to: "+ err.message )
projectsFailed.push( project.name )
}
})
)
res.json({'succeed':projectsCreated, 'failed': projectsFailed });
});
router.get('/api/admin/project/users', async function (req, res, next) {
try {
const users = await getProjectUsersACC(req.query.projectId, req.oAuthToken.access_token);
res.json(users);
} catch (err) {
next(err);
}
});
router.post('/api/admin/project/users', bodyParser.json(), async function (req, res, next) {
const projectId = req.body.projectId;
const users = {
'users': req.body.data
};
try {
const usersRes = await importProjectUsersACC(projectId, users, req.oAuthToken.access_token);
res.json(usersRes);
} catch (err) {
next(err);
}
});
module.exports = router;
And mount the router to our server application by modifying server.js
:
const express = require('express');
const session = require('cookie-session');
const { PORT, SERVER_SESSION_SECRET } = require('./config.js');
let app = express();
app.use(express.static('wwwroot'));
app.use(session({ secret: SERVER_SESSION_SECRET, maxAge: 24 * 60 * 60 * 1000 }));
app.use(require('./routes/auth.js'));
app.use(require('./routes/hubs.js'));
app.use(require('./routes/admin.js'));
app.listen(PORT, () => console.log(`Server listening on port ${PORT}...`));
Create a AdminController.cs file under the Controllers subfolder with the following content:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using Autodesk.Construction.AccountAdmin.Model;
using System.Linq;
[ApiController]
[Route("api/[controller]")]
public class AdminController : ControllerBase
{
private readonly ILogger<AdminController> _logger;
private readonly APS _aps;
public AdminController(ILogger<AdminController> logger, APS aps)
{
_logger = logger;
_aps = aps;
}
[HttpGet("projects")]
public async Task<ActionResult<string>> ListProjects()
{
var tokens = await AuthController.PrepareTokens(Request, Response, _aps);
if (tokens == null)
{
return Unauthorized();
}
var projects = await _aps.GetProjectsACC(Request.Query["accountId"], tokens);
return JsonConvert.SerializeObject(projects);
}
[HttpGet("project")]
public async Task<ActionResult<string>> ListProject(string projectId)
{
IList<Project> projectList=new List<Project>();
var tokens = await AuthController.PrepareTokens(Request, Response, _aps);
if (tokens == null)
{
return Unauthorized();
}
var projectInfo = await _aps.GetProjectACC(Request.Query["projectId"], tokens);
projectList.Add(projectInfo);
return JsonConvert.SerializeObject(projectList);
}
[HttpGet("project/users")]
public async Task<ActionResult<string>> ListProjectUsers(string projectId)
{
var tokens = await AuthController.PrepareTokens(Request, Response, _aps);
if (tokens == null)
{
return Unauthorized();
}
var projects = await _aps.GetProjectUsersACC(Request.Query["projectId"], tokens);
return JsonConvert.SerializeObject(projects);
}
[HttpPost("projects")]
public async Task<IActionResult> CreateProjects([FromBody] JObject content)
{
var tokens = await AuthController.PrepareTokens(Request, Response, _aps);
if (tokens == null)
{
return Unauthorized();
}
List<string> projectsCreated = new List<string>();
List<string> projectsFailed = new List<string>();
string accountId = content["accountId"].Value<string>();
var projects = (content["data"] as JArray)?.Select(p => (JObject)p).ToList();
var tasks = projects.Select(async project =>
{
try
{
var projectInfo = await _aps.CreateProjectACC(accountId, project, tokens);
projectsCreated.Add(projectInfo.Name);
while (projectInfo.Status != "active")
{
await Task.Delay(1000);
projectInfo = await _aps.GetProjectACC(projectInfo.Id, tokens);
}
var profile = await _aps.GetUserProfile(tokens);
await _aps.AddProjectAdminACC(projectInfo.Id, profile.Email, tokens);
}
catch (Exception ex)
{
Console.WriteLine($"Exception when creating project: {ex.Message}");
projectsFailed.Add(project["name"].Value<string>());
}
});
await Task.WhenAll(tasks);
return Ok(new { succeed = projectsCreated, failed = projectsFailed });
}
[HttpPost("project/users")]
public async Task<IActionResult> CreateProjectUsers([FromBody] JObject content)
{
var tokens = await AuthController.PrepareTokens(Request, Response, _aps);
if (tokens == null)
{
return Unauthorized();
}
string projectId = content["projectId"].Value<string>();
dynamic users = content["data"].Value<dynamic>();
dynamic body = new JObject();
body.users = users;
dynamic usersInfo = await _aps.ImportProjectUsersACC(projectId, body, tokens);
return Ok(new { UserInfo = usersInfo });
}
}
Create a AdminController.cs file under the Controllers subfolder with the following content:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using Autodesk.Construction.AccountAdmin.Model;
using System.Linq;
[ApiController]
[Route("api/[controller]")]
public class AdminController : ControllerBase
{
private readonly ILogger<AdminController> _logger;
private readonly APS _aps;
public AdminController(ILogger<AdminController> logger, APS aps)
{
_logger = logger;
_aps = aps;
}
[HttpGet("projects")]
public async Task<ActionResult<string>> ListProjects()
{
var tokens = await AuthController.PrepareTokens(Request, Response, _aps);
if (tokens == null)
{
return Unauthorized();
}
var projects = await _aps.GetProjectsACC(Request.Query["accountId"], tokens);
return JsonConvert.SerializeObject(projects);
}
[HttpGet("project")]
public async Task<ActionResult<string>> ListProject(string projectId)
{
IList<Project> projectList=new List<Project>();
var tokens = await AuthController.PrepareTokens(Request, Response, _aps);
if (tokens == null)
{
return Unauthorized();
}
var projectInfo = await _aps.GetProjectACC(Request.Query["projectId"], tokens);
projectList.Add(projectInfo);
return JsonConvert.SerializeObject(projectList);
}
[HttpGet("project/users")]
public async Task<ActionResult<string>> ListProjectUsers(string projectId)
{
var tokens = await AuthController.PrepareTokens(Request, Response, _aps);
if (tokens == null)
{
return Unauthorized();
}
var projects = await _aps.GetProjectUsersACC(Request.Query["projectId"], tokens);
return JsonConvert.SerializeObject(projects);
}
[HttpPost("projects")]
public async Task<IActionResult> CreateProjects([FromBody] JObject content)
{
var tokens = await AuthController.PrepareTokens(Request, Response, _aps);
if (tokens == null)
{
return Unauthorized();
}
List<string> projectsCreated = new List<string>();
List<string> projectsFailed = new List<string>();
string accountId = content["accountId"].Value<string>();
var projects = (content["data"] as JArray)?.Select(p => (JObject)p).ToList();
var tasks = projects.Select(async project =>
{
try
{
var projectInfo = await _aps.CreateProjectACC(accountId, project, tokens);
projectsCreated.Add(projectInfo.Name);
while (projectInfo.Status != "active")
{
await Task.Delay(1000);
projectInfo = await _aps.GetProjectACC(projectInfo.Id, tokens);
}
var profile = await _aps.GetUserProfile(tokens);
await _aps.AddProjectAdminACC(projectInfo.Id, profile.Email, tokens);
}
catch (Exception ex)
{
Console.WriteLine($"Exception when creating project: {ex.Message}");
projectsFailed.Add(project["name"].Value<string>());
}
});
await Task.WhenAll(tasks);
return Ok(new { succeed = projectsCreated, failed = projectsFailed });
}
[HttpPost("project/users")]
public async Task<IActionResult> CreateProjectUsers([FromBody] JObject content)
{
var tokens = await AuthController.PrepareTokens(Request, Response, _aps);
if (tokens == null)
{
return Unauthorized();
}
string projectId = content["projectId"].Value<string>();
dynamic users = content["data"].Value<dynamic>();
dynamic body = new JObject();
body.users = users;
dynamic usersInfo = await _aps.ImportProjectUsersACC(projectId, body, tokens);
return Ok(new { UserInfo = usersInfo });
}
}
Try it out
And that's it for the server side. Time to try it out!
Start (or restart) the app from Visual Studio Code as usual,
- Use the ID(removing b.) of one ACC hub as you get in previous step in the address: http://localhost:8080/api/admin/projects?accountId={your-account-id}. In this case the server application should respond with a JSON list of all projects available under the specified hub.
- Use the project ID as you get in previous step, try to call the address: http://localhost:8080/api/admin/project/users?projectId={your-project-id}, the server application should respond with a JSON list of all the users from this project.
You may get different project list by the 2 differnt endpoints, here are the explain:
- With Data Management API, it will return all the projects that the current user is involved.
- With Account Admin API, it will return all the projects in the account if the current user is Account Admin.