[bugfix] Fix account roles (#1542)

* Change account role from string to object

* Update tests

* small fixes + swagger docs

---------

Co-authored-by: zowhoey <11893985+zowhoey@users.noreply.github.com>
This commit is contained in:
tobi 2023-02-20 17:00:44 +01:00 committed by GitHub
parent b6143c9ab8
commit e8a04b7ce1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 160 additions and 73 deletions

View File

@ -256,12 +256,7 @@ definitions:
type: string type: string
x-go-name: Note x-go-name: Note
role: role:
description: |- $ref: '#/definitions/accountRole'
Role of the account on this instance.
Omitted for remote accounts.
example: user
type: string
x-go-name: Role
source: source:
$ref: '#/definitions/Source' $ref: '#/definitions/Source'
statuses_count: statuses_count:
@ -346,6 +341,15 @@ definitions:
type: object type: object
x-go-name: Relationship x-go-name: Relationship
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
accountRole:
properties:
name:
type: string
x-go-name: Name
title: AccountRole models the role of an account.
type: object
x-go-name: AccountRole
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
adminAccountInfo: adminAccountInfo:
properties: properties:
account: account:
@ -423,9 +427,7 @@ definitions:
type: string type: string
x-go-name: Locale x-go-name: Locale
role: role:
description: The current role of the account. $ref: '#/definitions/accountRole'
type: string
x-go-name: Role
silenced: silenced:
description: Whether the account is currently silenced description: Whether the account is currently silenced
type: boolean type: boolean

View File

@ -156,7 +156,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet1() {
"ips": [], "ips": [],
"locale": "", "locale": "",
"invite_request": null, "invite_request": null,
"role": "user", "role": {
"name": "user"
},
"confirmed": false, "confirmed": false,
"approved": false, "approved": false,
"disabled": false, "disabled": false,
@ -195,7 +197,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet1() {
"ips": [], "ips": [],
"locale": "en", "locale": "en",
"invite_request": "", "invite_request": "",
"role": "user", "role": {
"name": "user"
},
"confirmed": true, "confirmed": true,
"approved": true, "approved": true,
"disabled": false, "disabled": false,
@ -222,7 +226,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet1() {
"last_status_at": "2021-10-20T10:40:37.000Z", "last_status_at": "2021-10-20T10:40:37.000Z",
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"role": "user" "role": {
"name": "user"
}
}, },
"created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG" "created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG"
}, },
@ -236,7 +242,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet1() {
"ips": [], "ips": [],
"locale": "en", "locale": "en",
"invite_request": "", "invite_request": "",
"role": "admin", "role": {
"name": "admin"
},
"confirmed": true, "confirmed": true,
"approved": true, "approved": true,
"disabled": false, "disabled": false,
@ -264,7 +272,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet1() {
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "admin" "role": {
"name": "admin"
}
}, },
"created_by_application_id": "01F8MGXQRHYF5QPMTMXP78QC2F" "created_by_application_id": "01F8MGXQRHYF5QPMTMXP78QC2F"
}, },
@ -278,7 +288,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet1() {
"ips": [], "ips": [],
"locale": "en", "locale": "en",
"invite_request": "", "invite_request": "",
"role": "admin", "role": {
"name": "admin"
},
"confirmed": true, "confirmed": true,
"approved": true, "approved": true,
"disabled": false, "disabled": false,
@ -306,7 +318,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet1() {
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "admin" "role": {
"name": "admin"
}
}, },
"created_by_application_id": "01F8MGXQRHYF5QPMTMXP78QC2F" "created_by_application_id": "01F8MGXQRHYF5QPMTMXP78QC2F"
}, },
@ -333,7 +347,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet1() {
"ips": [], "ips": [],
"locale": "en", "locale": "en",
"invite_request": "", "invite_request": "",
"role": "user", "role": {
"name": "user"
},
"confirmed": true, "confirmed": true,
"approved": true, "approved": true,
"disabled": false, "disabled": false,
@ -360,7 +376,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet1() {
"last_status_at": "2021-10-20T10:40:37.000Z", "last_status_at": "2021-10-20T10:40:37.000Z",
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"role": "user" "role": {
"name": "user"
}
}, },
"created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG" "created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG"
}, },
@ -374,7 +392,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet1() {
"ips": [], "ips": [],
"locale": "", "locale": "",
"invite_request": null, "invite_request": null,
"role": "user", "role": {
"name": "user"
},
"confirmed": false, "confirmed": false,
"approved": false, "approved": false,
"disabled": false, "disabled": false,
@ -528,7 +548,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet2() {
"ips": [], "ips": [],
"locale": "en", "locale": "en",
"invite_request": "", "invite_request": "",
"role": "user", "role": {
"name": "user"
},
"confirmed": true, "confirmed": true,
"approved": true, "approved": true,
"disabled": false, "disabled": false,
@ -555,7 +577,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet2() {
"last_status_at": "2021-10-20T10:40:37.000Z", "last_status_at": "2021-10-20T10:40:37.000Z",
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"role": "user" "role": {
"name": "user"
}
}, },
"created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG" "created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG"
}, },
@ -569,7 +593,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet2() {
"ips": [], "ips": [],
"locale": "", "locale": "",
"invite_request": null, "invite_request": null,
"role": "user", "role": {
"name": "user"
},
"confirmed": false, "confirmed": false,
"approved": false, "approved": false,
"disabled": false, "disabled": false,
@ -723,7 +749,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet3() {
"ips": [], "ips": [],
"locale": "en", "locale": "en",
"invite_request": "", "invite_request": "",
"role": "user", "role": {
"name": "user"
},
"confirmed": true, "confirmed": true,
"approved": true, "approved": true,
"disabled": false, "disabled": false,
@ -750,7 +778,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet3() {
"last_status_at": "2021-10-20T10:40:37.000Z", "last_status_at": "2021-10-20T10:40:37.000Z",
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"role": "user" "role": {
"name": "user"
}
}, },
"created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG" "created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG"
}, },
@ -764,7 +794,9 @@ func (suite *ReportsGetTestSuite) TestReportsGet3() {
"ips": [], "ips": [],
"locale": "", "locale": "",
"invite_request": null, "invite_request": null,
"role": "user", "role": {
"name": "user"
},
"confirmed": false, "confirmed": false,
"approved": false, "approved": false,
"disabled": false, "disabled": false,

View File

@ -151,7 +151,9 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() {
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "admin" "role": {
"name": "admin"
}
}, },
"max_toot_chars": 5000 "max_toot_chars": 5000
}`, dst.String()) }`, dst.String())
@ -247,7 +249,9 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() {
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "admin" "role": {
"name": "admin"
}
}, },
"max_toot_chars": 5000 "max_toot_chars": 5000
}`, dst.String()) }`, dst.String())
@ -343,7 +347,9 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() {
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "admin" "role": {
"name": "admin"
}
}, },
"max_toot_chars": 5000 "max_toot_chars": 5000
}`, dst.String()) }`, dst.String())
@ -490,7 +496,9 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() {
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "admin" "role": {
"name": "admin"
}
}, },
"max_toot_chars": 5000 "max_toot_chars": 5000
}`, dst.String()) }`, dst.String())
@ -609,7 +617,9 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() {
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "admin" "role": {
"name": "admin"
}
}, },
"max_toot_chars": 5000 "max_toot_chars": 5000
}`, dst.String()) }`, dst.String())
@ -740,7 +750,9 @@ func (suite *InstancePatchTestSuite) TestInstancePatch9() {
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "admin" "role": {
"name": "admin"
}
}, },
"max_toot_chars": 5000 "max_toot_chars": 5000
}`, dst.String()) }`, dst.String())

View File

@ -96,8 +96,7 @@ type Account struct {
EnableRSS bool `json:"enable_rss,omitempty"` EnableRSS bool `json:"enable_rss,omitempty"`
// Role of the account on this instance. // Role of the account on this instance.
// Omitted for remote accounts. // Omitted for remote accounts.
// example: user Role *AccountRole `json:"role,omitempty"`
Role AccountRole `json:"role,omitempty"`
} }
// AccountCreateRequest models account creation parameters. // AccountCreateRequest models account creation parameters.
@ -215,13 +214,19 @@ type AccountDeleteRequest struct {
// AccountRole models the role of an account. // AccountRole models the role of an account.
// //
// swagger:enum accountRole // swagger:model accountRole
type AccountRole struct {
Name AccountRoleName `json:"name"`
}
// AccountRoleName represent the name of the role of an account.
//
// swagger:type string // swagger:type string
type AccountRole string type AccountRoleName string
const ( const (
AccountRoleUser AccountRole = "user" // Standard user AccountRoleUser AccountRoleName = "user" // Standard user
AccountRoleModerator AccountRole = "moderator" // Moderator privileges AccountRoleModerator AccountRoleName = "moderator" // Moderator privileges
AccountRoleAdmin AccountRole = "admin" // Instance admin AccountRoleAdmin AccountRoleName = "admin" // Instance admin
AccountRoleUnknown AccountRole = "" // We don't know / remote account AccountRoleUnknown AccountRoleName = "" // We don't know / remote account
) )

View File

@ -56,7 +56,7 @@ type AdminAccountInfo struct {
// example: Pleaaaaaaaaaaaaaaase!! // example: Pleaaaaaaaaaaaaaaase!!
InviteRequest *string `json:"invite_request"` InviteRequest *string `json:"invite_request"`
// The current role of the account. // The current role of the account.
Role string `json:"role"` Role AccountRole `json:"role"`
// Whether the account has confirmed their email address. // Whether the account has confirmed their email address.
Confirmed bool `json:"confirmed"` Confirmed bool `json:"confirmed"`
// Whether the account is currently approved. // Whether the account is currently approved.

View File

@ -167,10 +167,8 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
log.Errorf(ctx, "error converting account emojis: %v", err) log.Errorf(ctx, "error converting account emojis: %v", err)
} }
var ( var acct string
acct string var role *apimodel.AccountRole
role = apimodel.AccountRoleUnknown
)
if a.Domain != "" { if a.Domain != "" {
// this is a remote user // this is a remote user
@ -185,11 +183,11 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
switch { switch {
case *user.Admin: case *user.Admin:
role = apimodel.AccountRoleAdmin role = &apimodel.AccountRole{Name: apimodel.AccountRoleAdmin}
case *user.Moderator: case *user.Moderator:
role = apimodel.AccountRoleModerator role = &apimodel.AccountRole{Name: apimodel.AccountRoleModerator}
default: default:
role = apimodel.AccountRoleUser role = &apimodel.AccountRole{Name: apimodel.AccountRoleUser}
} }
} }
@ -270,7 +268,7 @@ func (c *converter) AccountToAdminAPIAccount(ctx context.Context, a *gtsmodel.Ac
disabled bool disabled bool
silenced bool silenced bool
suspended bool suspended bool
role apimodel.AccountRole = apimodel.AccountRoleUser // assume user by default role = apimodel.AccountRole{Name: apimodel.AccountRoleUser} // assume user by default
createdByApplicationID string createdByApplicationID string
) )
@ -296,9 +294,9 @@ func (c *converter) AccountToAdminAPIAccount(ctx context.Context, a *gtsmodel.Ac
locale = user.Locale locale = user.Locale
inviteRequest = &user.Account.Reason inviteRequest = &user.Account.Reason
if *user.Admin { if *user.Admin {
role = apimodel.AccountRoleAdmin role.Name = apimodel.AccountRoleAdmin
} else if *user.Moderator { } else if *user.Moderator {
role = apimodel.AccountRoleModerator role.Name = apimodel.AccountRoleModerator
} }
confirmed = !user.ConfirmedAt.IsZero() confirmed = !user.ConfirmedAt.IsZero()
approved = *user.Approved approved = *user.Approved
@ -323,7 +321,7 @@ func (c *converter) AccountToAdminAPIAccount(ctx context.Context, a *gtsmodel.Ac
IPs: []interface{}{}, // not implemented, IPs: []interface{}{}, // not implemented,
Locale: locale, Locale: locale,
InviteRequest: inviteRequest, InviteRequest: inviteRequest,
Role: string(role), Role: role,
Confirmed: confirmed, Confirmed: confirmed,
Approved: approved, Approved: approved,
Disabled: disabled, Disabled: disabled,

View File

@ -63,7 +63,9 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontend() {
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "user" "role": {
"name": "user"
}
}`, string(b)) }`, string(b))
} }
@ -109,7 +111,9 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiStruct()
], ],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "user" "role": {
"name": "user"
}
}`, string(b)) }`, string(b))
} }
@ -155,7 +159,9 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiIDs() {
], ],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "user" "role": {
"name": "user"
}
}`, string(b)) }`, string(b))
} }
@ -198,7 +204,9 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendSensitive() {
"follow_requests_count": 0 "follow_requests_count": 0
}, },
"enable_rss": true, "enable_rss": true,
"role": "user" "role": {
"name": "user"
}
}`, string(b)) }`, string(b))
} }
@ -258,7 +266,9 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontend() {
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "admin" "role": {
"name": "admin"
}
}, },
"media_attachments": [ "media_attachments": [
{ {
@ -371,7 +381,9 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontendUnknownLanguage()
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "admin" "role": {
"name": "admin"
}
}, },
"media_attachments": [ "media_attachments": [
{ {
@ -553,7 +565,9 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV1ToFrontend() {
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "admin" "role": {
"name": "admin"
}
}, },
"max_toot_chars": 5000 "max_toot_chars": 5000
}`, string(b)) }`, string(b))
@ -660,7 +674,9 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV2ToFrontend() {
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "admin" "role": {
"name": "admin"
}
} }
}, },
"rules": [] "rules": []
@ -811,7 +827,9 @@ func (suite *InternalToFrontendTestSuite) TestReportToFrontend2() {
"last_status_at": "2021-10-20T10:40:37.000Z", "last_status_at": "2021-10-20T10:40:37.000Z",
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"role": "user" "role": {
"name": "user"
}
} }
}`, string(b)) }`, string(b))
} }
@ -843,7 +861,9 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() {
"ips": [], "ips": [],
"locale": "", "locale": "",
"invite_request": null, "invite_request": null,
"role": "user", "role": {
"name": "user"
},
"confirmed": false, "confirmed": false,
"approved": false, "approved": false,
"disabled": false, "disabled": false,
@ -882,7 +902,9 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() {
"ips": [], "ips": [],
"locale": "en", "locale": "en",
"invite_request": "", "invite_request": "",
"role": "user", "role": {
"name": "user"
},
"confirmed": true, "confirmed": true,
"approved": true, "approved": true,
"disabled": false, "disabled": false,
@ -909,7 +931,9 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() {
"last_status_at": "2021-10-20T10:40:37.000Z", "last_status_at": "2021-10-20T10:40:37.000Z",
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"role": "user" "role": {
"name": "user"
}
}, },
"created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG" "created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG"
}, },
@ -923,7 +947,9 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() {
"ips": [], "ips": [],
"locale": "en", "locale": "en",
"invite_request": "", "invite_request": "",
"role": "admin", "role": {
"name": "admin"
},
"confirmed": true, "confirmed": true,
"approved": true, "approved": true,
"disabled": false, "disabled": false,
@ -951,7 +977,9 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() {
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "admin" "role": {
"name": "admin"
}
}, },
"created_by_application_id": "01F8MGXQRHYF5QPMTMXP78QC2F" "created_by_application_id": "01F8MGXQRHYF5QPMTMXP78QC2F"
}, },
@ -965,7 +993,9 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() {
"ips": [], "ips": [],
"locale": "en", "locale": "en",
"invite_request": "", "invite_request": "",
"role": "admin", "role": {
"name": "admin"
},
"confirmed": true, "confirmed": true,
"approved": true, "approved": true,
"disabled": false, "disabled": false,
@ -993,7 +1023,9 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() {
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"enable_rss": true, "enable_rss": true,
"role": "admin" "role": {
"name": "admin"
}
}, },
"created_by_application_id": "01F8MGXQRHYF5QPMTMXP78QC2F" "created_by_application_id": "01F8MGXQRHYF5QPMTMXP78QC2F"
}, },
@ -1030,7 +1062,9 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend2() {
"ips": [], "ips": [],
"locale": "en", "locale": "en",
"invite_request": "", "invite_request": "",
"role": "user", "role": {
"name": "user"
},
"confirmed": true, "confirmed": true,
"approved": true, "approved": true,
"disabled": false, "disabled": false,
@ -1057,7 +1091,9 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend2() {
"last_status_at": "2021-10-20T10:40:37.000Z", "last_status_at": "2021-10-20T10:40:37.000Z",
"emojis": [], "emojis": [],
"fields": [], "fields": [],
"role": "user" "role": {
"name": "user"
}
}, },
"created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG" "created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG"
}, },
@ -1071,7 +1107,9 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend2() {
"ips": [], "ips": [],
"locale": "", "locale": "",
"invite_request": null, "invite_request": null,
"role": "user", "role": {
"name": "user"
},
"confirmed": false, "confirmed": false,
"approved": false, "approved": false,
"disabled": false, "disabled": false,

View File

@ -55,7 +55,7 @@ const nav = {
const { sidebar, panelRouter } = require("./lib/get-views")(nav); const { sidebar, panelRouter } = require("./lib/get-views")(nav);
function App({ account }) { function App({ account }) {
const isAdmin = account.role == "admin"; const isAdmin = account.role.name == "admin";
const [logoutQuery] = query.useLogoutMutation(); const [logoutQuery] = query.useLogoutMutation();
return ( return (

View File

@ -90,7 +90,7 @@ function UserProfileForm({ data: profile }) {
header={form.header.previewValue ?? profile.header} header={form.header.previewValue ?? profile.header}
display_name={form.displayName.value ?? profile.username} display_name={form.displayName.value ?? profile.username}
username={profile.username} username={profile.username}
role={profile.role} role={profile.role.name}
/> />
<div className="files"> <div className="files">
<div> <div>

View File

@ -34,7 +34,7 @@
<div class="usernamecontainer"> <div class="usernamecontainer">
<div class="username">@{{ .account.Username }}@{{ .instance.AccountDomain }}</div> <div class="username">@{{ .account.Username }}@{{ .instance.AccountDomain }}</div>
{{- /* Only render account role if 1. it's present and 2. it's not equal to the standard 'user' role */ -}} {{- /* Only render account role if 1. it's present and 2. it's not equal to the standard 'user' role */ -}}
{{ if and (.account.Role) (ne .account.Role "user") }}<div class="role {{ .account.Role }}">{{ .account.Role }}</div>{{ end }} {{ if and (.account.Role) (ne .account.Role.Name "user") }}<div class="role {{ .account.Role.Name }}">{{ .account.Role.Name }}</div>{{ end }}
</div> </div>
</div> </div>
<div class="detailed"> <div class="detailed">