groundwork for generating member export (unfinished)
This commit is contained in:
+27
-10
@@ -91,16 +91,17 @@ type CfgPermission struct {
|
|||||||
|
|
||||||
// CfgSecurityDefs is the master structure for security definitions.
|
// CfgSecurityDefs is the master structure for security definitions.
|
||||||
type CfgSecurityDefs struct {
|
type CfgSecurityDefs struct {
|
||||||
Scopes []CfgScope `yaml:"scopes"`
|
Scopes []CfgScope `yaml:"scopes"`
|
||||||
Roles []CfgRole `yaml:"roles"`
|
Roles []CfgRole `yaml:"roles"`
|
||||||
Defaults []CfgDefault `yaml:"defaults"`
|
Defaults []CfgDefault `yaml:"defaults"`
|
||||||
Lists []CfgRoleList `yaml:"lists"`
|
Lists []CfgRoleList `yaml:"lists"`
|
||||||
Permissions []CfgPermission `yaml:"permissions"`
|
Permissions []CfgPermission `yaml:"permissions"`
|
||||||
scopeMap map[string]*CfgScope
|
scopeMap map[string]*CfgScope
|
||||||
roleMap map[string]*CfgRole
|
roleMap map[string]*CfgRole
|
||||||
defaultsMap map[string]*CfgDefault
|
roleMapReverse map[uint16]*CfgRole
|
||||||
listsMap map[string]*CfgRoleList
|
defaultsMap map[string]*CfgDefault
|
||||||
permsMap map[string]*CfgPermission
|
listsMap map[string]*CfgRoleList
|
||||||
|
permsMap map[string]*CfgPermission
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed securitydefs.yaml
|
//go:embed securitydefs.yaml
|
||||||
@@ -153,10 +154,12 @@ func init() {
|
|||||||
securityRoot.scopeMap[sc.Name] = &(securityRoot.Scopes[i])
|
securityRoot.scopeMap[sc.Name] = &(securityRoot.Scopes[i])
|
||||||
}
|
}
|
||||||
securityRoot.roleMap = make(map[string]*CfgRole)
|
securityRoot.roleMap = make(map[string]*CfgRole)
|
||||||
|
securityRoot.roleMapReverse = make(map[uint16]*CfgRole)
|
||||||
for i, ro := range securityRoot.Roles {
|
for i, ro := range securityRoot.Roles {
|
||||||
scope := securityRoot.scopeMap[ro.Scope]
|
scope := securityRoot.scopeMap[ro.Scope]
|
||||||
securityRoot.Roles[i].level = parseLevelValue(scope.bounds, ro.Value)
|
securityRoot.Roles[i].level = parseLevelValue(scope.bounds, ro.Value)
|
||||||
securityRoot.roleMap[ro.Internal] = &(securityRoot.Roles[i])
|
securityRoot.roleMap[ro.Internal] = &(securityRoot.Roles[i])
|
||||||
|
securityRoot.roleMapReverse[securityRoot.Roles[i].level] = &(securityRoot.Roles[i])
|
||||||
}
|
}
|
||||||
securityRoot.defaultsMap = make(map[string]*CfgDefault)
|
securityRoot.defaultsMap = make(map[string]*CfgDefault)
|
||||||
for i, def := range securityRoot.Defaults {
|
for i, def := range securityRoot.Defaults {
|
||||||
@@ -247,6 +250,20 @@ func AmRole(id string) Role {
|
|||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* AmRoleForLevel returns a Role given an integer level.
|
||||||
|
* Parameters:
|
||||||
|
* level - Level of the Role to look up.
|
||||||
|
* Returns:
|
||||||
|
* The specified role.
|
||||||
|
*/
|
||||||
|
func AmRoleForLevel(level uint16) Role {
|
||||||
|
rc, ok := securityRoot.roleMapReverse[level]
|
||||||
|
if !ok {
|
||||||
|
log.Errorf("AmRoleForLevel('%d') - role not found!", level)
|
||||||
|
}
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
/* AmDefaultRole returns a Role given a default ID.
|
/* AmDefaultRole returns a Role given a default ID.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* id - ID of the default to look up.
|
* id - ID of the default to look up.
|
||||||
|
|||||||
@@ -123,6 +123,14 @@ lists:
|
|||||||
- "Global.Normal"
|
- "Global.Normal"
|
||||||
- "UnrestrictedUser"
|
- "UnrestrictedUser"
|
||||||
- "Global.PFY"
|
- "Global.PFY"
|
||||||
|
- name: "Global.AllUserLevels"
|
||||||
|
roles:
|
||||||
|
- "Global.Anonymous"
|
||||||
|
- "Global.Unverified"
|
||||||
|
- "Global.Normal"
|
||||||
|
- "UnrestrictedUser"
|
||||||
|
- "Global.PFY"
|
||||||
|
- "Global.BOFH"
|
||||||
- name: "Global.CreateCommunity"
|
- name: "Global.CreateCommunity"
|
||||||
default: "Global.Normal"
|
default: "Global.Normal"
|
||||||
roles:
|
roles:
|
||||||
|
|||||||
@@ -121,6 +121,22 @@ func (p *UserPrefs) Location() *time.Location {
|
|||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LocationISO8601Offset returns an offset value for the user's time location.
|
||||||
|
func (p *UserPrefs) LocationISO8601Offset() string {
|
||||||
|
loc := p.Location()
|
||||||
|
_, secondsOut := time.Now().In(loc).Zone()
|
||||||
|
if secondsOut == 0 {
|
||||||
|
return "Z"
|
||||||
|
}
|
||||||
|
minutesOut := secondsOut / 60
|
||||||
|
if minutesOut < 0 {
|
||||||
|
minutesOut = -minutesOut
|
||||||
|
return fmt.Sprintf("-%02d:%02d", minutesOut/60, minutesOut%60)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("+%02d:%02d", minutesOut/60, minutesOut%60)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// User represents a user in the Amsterdam database.
|
// User represents a user in the Amsterdam database.
|
||||||
type User struct {
|
type User struct {
|
||||||
Mutex sync.RWMutex
|
Mutex sync.RWMutex
|
||||||
|
|||||||
@@ -0,0 +1,272 @@
|
|||||||
|
/*
|
||||||
|
* Amsterdam Web Communities System
|
||||||
|
* Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
// Package exports contains interfacing code for external data formats.
|
||||||
|
package exports
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
|
"git.erbosoft.com/amy/amsterdam/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file defines data structures for XML vCards as defined by the XMPP Foundation specification XEP-0054, "vcard-temp",
|
||||||
|
* https://xmpp.org/extensions/xep-0054.html.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// VCard is the top level vCard structure.
|
||||||
|
type VCard struct {
|
||||||
|
XMLName xml.Name `xml:"vcard-temp vCard"` // name must be "vCard" in the "vcard-temp" namespace
|
||||||
|
Version string `xml:"VERSION"` // vCard version number
|
||||||
|
FullName string `xml:"FN"` // full name
|
||||||
|
Name VCName `xml:"N"` // broken-up name components
|
||||||
|
Nickname string `xml:"NICKNAME"` // nickname (not used in Amsterdam)
|
||||||
|
Photo *VCPhoto `xml:"PHOTO"` // user photo (not used in Amsterdam)
|
||||||
|
BDay string `xml:"BDAY"` // birthday, ISO 8601 format
|
||||||
|
Address *[]VCAddress `xml:"ADR"` // address
|
||||||
|
AddressLabel *[]VCAddressLabel `xml:"LABEL"` // address label
|
||||||
|
Email *[]VCEmail `xml:"EMAIL"` // E-mail address
|
||||||
|
Tel *[]VCTelephone `xml:"TEL"` // telephone number
|
||||||
|
JabberID string `xml:"JABBERID"` // Jabber/XMPP address (user@host) (XMPP extension) (not used in Amsterdam)
|
||||||
|
Mailer string `xml:"MAILER"` // mailer user agent (not used in Amsterdam)
|
||||||
|
TZ string `xml:"TZ"` // time zone indicator, ISO 8601 formatted UTC offset
|
||||||
|
Geolocation *VCGeolocation `xml:"GEO"` // geolocation (not used in Amsterdam)
|
||||||
|
Title string `xml:"TITLE"` // job title (not used in Amsterdam)
|
||||||
|
Role string `xml:"ROLE"` // job role (not used in Amsterdam)
|
||||||
|
Logo *VCLogo `xml:"LOGO"` // organization logo (not used in Amsterdam)
|
||||||
|
Agent *VCAgent `xml:"AGENT"` // agent for the organization (not used in Amsterdam)
|
||||||
|
Org *VCOrganization `xml:"ORG"` // organization
|
||||||
|
Categories *VCCategory `xml:"CATEGORIES"` // categories
|
||||||
|
Note string `xml:"NOTE"` // text note (not used by Amsterdam)
|
||||||
|
ProductID string `xml:"PRODID"` // product ID that generated this vCard (not used by Amsterdam)
|
||||||
|
LastUpdate string `xml:"REV"` // last update to this information, ISO 8601 format
|
||||||
|
SortString string `xml:"SORT-STRING"` // sort string
|
||||||
|
Sound *VCSound `xml:"SOUND"` // pronunciation property (not used in Amsterdam)
|
||||||
|
UID string `xml:"UID"` // unique identifier (not necessarily an Amsterdam UID!) (not used in Amsterdam)
|
||||||
|
URL string `xml:"URL"` // URL
|
||||||
|
Class *VCClass `xml:"CLASS"` // privacy classification (not used in Amsterdam)
|
||||||
|
Key *VCKey `xml:"KEY"` // authentication credential or encryption key (not used in Amsterdam)
|
||||||
|
Description string `xml:"DESC"` // description string value (XMPP extension)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VCPhoto is the "photo" attachment to the VCard.
|
||||||
|
type VCPhoto struct {
|
||||||
|
XMLName xml.Name `xml:"PHOTO"` // must be a "PHOTO" tag
|
||||||
|
Type string `xml:"TYPE"` // data type for BINVAL
|
||||||
|
BinaryValue string `xml:"BINVAL"` // binary photo value (Base64 encoded)
|
||||||
|
ExternalValue string `xml:"EXTVAL"` // external value of photo (URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name is the "structured name" property of the vCard.
|
||||||
|
type VCName struct {
|
||||||
|
XMLName xml.Name `xml:"N"` // must be an "N" tag
|
||||||
|
Family string `xml:"FAMILY"` // family name
|
||||||
|
Given string `xml:"GIVEN"` // given name
|
||||||
|
Middle string `xml:"MIDDLE"` // middle name/initial
|
||||||
|
Prefix string `xml:"PREFIX"` // prefix
|
||||||
|
Suffix string `xml:"SUFFIX"` // suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
// VCAddress is the "address" property of the vCard.
|
||||||
|
type VCAddress struct {
|
||||||
|
XMLName xml.Name `xml:"ADR"` // must be a "ADR" tag
|
||||||
|
Work xml.Name `xml:"WORK"` // Presence indicates work address
|
||||||
|
Home xml.Name `xml:"HOME"` // Presence indicates home address
|
||||||
|
Postal xml.Name `xml:"POSTAL"` // presence indicates postal address
|
||||||
|
Parcel xml.Name `xml:"PARCEL"` // presence indicates parcel address
|
||||||
|
Domestic xml.Name `xml:"DOM"` // presence indicates domestic address
|
||||||
|
International xml.Name `xml:"INTL"` // presence indicates international address
|
||||||
|
Preferred xml.Name `xml:"PREF"` // Presence indicates preferred address
|
||||||
|
POBox string `xml:"POBOX"` // post office box
|
||||||
|
Locality string `xml:"LOCALITY"` // locality (city)
|
||||||
|
Region string `xml:"REGION"` // region (state/province)
|
||||||
|
PCode string `xml:"PCODE"` // postal code
|
||||||
|
Country string `xml:"CTRY"` // country
|
||||||
|
Street string `xml:"STREET"` // street address (addr line 1)
|
||||||
|
ExtAddr string `xml:"EXTADR"` // extended address (addr line 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VCAddressLabel is the "address label" property of the vCard.
|
||||||
|
type VCAddressLabel struct {
|
||||||
|
XMLName xml.Name `xml:"LABEL"` // must be a "LABEL" tag
|
||||||
|
Work xml.Name `xml:"WORK"` // Presence indicates work address
|
||||||
|
Home xml.Name `xml:"HOME"` // Presence indicates home address
|
||||||
|
Postal xml.Name `xml:"POSTAL"` // presence indicates postal address
|
||||||
|
Parcel xml.Name `xml:"PARCEL"` // presence indicates parcel address
|
||||||
|
Domestic xml.Name `xml:"DOM"` // presence indicates domestic address
|
||||||
|
International xml.Name `xml:"INTL"` // presence indicates international address
|
||||||
|
Preferred xml.Name `xml:"PREF"` // Presence indicates preferred address
|
||||||
|
Lines []string `xml:"LINE"` // lines of text on the address label
|
||||||
|
}
|
||||||
|
|
||||||
|
// VCEmail is the "E-mail address" property of the vCard.
|
||||||
|
type VCEmail struct {
|
||||||
|
XMLName xml.Name `xml:"EMAIL"` // must be an "EMAIL" tag
|
||||||
|
Work xml.Name `xml:"WORK"` // presence indicates work address
|
||||||
|
Home xml.Name `xml:"HOME"` // presence indicates home address
|
||||||
|
Internet xml.Name `xml:"INTERNET"` // presence indicates Internet address
|
||||||
|
Preferred xml.Name `xml:"PREF"` // Presence indicates preferred address
|
||||||
|
X400 xml.Name `xml:"X400"` // Presence indicates X.400 address
|
||||||
|
UserID string `xml:"USERID"` // user ID (address)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VCTelephone is the "telephone number" property of the vCard.
|
||||||
|
type VCTelephone struct {
|
||||||
|
XMLName xml.Name `xml:"TEL"` // must be a "TEL" tag
|
||||||
|
Work xml.Name `xml:"WORK"` // presence indicates work number
|
||||||
|
Home xml.Name `xml:"HOME"` // presence indicates home number
|
||||||
|
Voice xml.Name `xml:"VOICE"` // presence indicates voice number
|
||||||
|
Fax xml.Name `xml:"FAX"` // presence indicates fax number
|
||||||
|
Pager xml.Name `xml:"PAGER"` // presence indicates pager number
|
||||||
|
Message xml.Name `xml:"MSG"` // presence indicates message number
|
||||||
|
Cell xml.Name `xml:"CELL"` // presence indicates cellphone number
|
||||||
|
Video xml.Name `xml:"VIDEO"` // presence indicates videophone number
|
||||||
|
BBS xml.Name `xml:"BBS"` // presence indicates BBS number
|
||||||
|
Modem xml.Name `xml:"MODEM"` // presence indicates modem number
|
||||||
|
ISDN xml.Name `xml:"ISDN"` // presence indicates ISDN number
|
||||||
|
PCS xml.Name `xml:"PCS"` // presence indicates PCS number
|
||||||
|
Preferred xml.Name `xml:"PREF"` // presence indicates preferred number
|
||||||
|
Number string `xml:"NUMBER"` // the number
|
||||||
|
}
|
||||||
|
|
||||||
|
// VCGeolocation is the "geolocation" property of the vCard.
|
||||||
|
type VCGeolocation struct {
|
||||||
|
XMLName xml.Name `xml:"GEO"` // must be a "GEO" tag
|
||||||
|
Latitude string `xml:"LAT"` // latitude to six decimal places (North is positive)
|
||||||
|
Longitude string `xml:"LONG"` // longitude to six decimal places (East is positive)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VCLogo is the "logo" property of the vCard.
|
||||||
|
type VCLogo struct {
|
||||||
|
XMLName xml.Name `xml:"LOGO"` // must be a "LOGO" tag
|
||||||
|
Type string `xml:"TYPE"` // data type for BINVAL
|
||||||
|
BinaryValue string `xml:"BINVAL"` // binary photo value (Base64 encoded)
|
||||||
|
ExternalValue string `xml:"EXTVAL"` // external value of photo (URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VCAgent is the "agent" property of the vCard.
|
||||||
|
type VCAgent struct {
|
||||||
|
XMLName xml.Name `xml:"AGENT"` // must be an "AGENT" tag
|
||||||
|
VCard *VCard `xml:"vcard-temp vCard"` // vCard with agent contact info
|
||||||
|
ExternalValue string `xml:"EXTVAL"` // external value, such as URL to contact info
|
||||||
|
}
|
||||||
|
|
||||||
|
// VCOrganization is the "organization" property of the vCard.
|
||||||
|
type VCOrganization struct {
|
||||||
|
XMLName xml.Name `xml:"ORG"` // must be an "ORG" tag
|
||||||
|
OrgName string `xml:"ORGNAME"` // organization name
|
||||||
|
OrgUnit *[]string `xml:"ORGUNIT"` // organization unit(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VCCategory is the "category" property of the vCard.
|
||||||
|
type VCCategory struct {
|
||||||
|
XMLName xml.Name `xml:"CATEGORIES"` // must be a "CATEGORIES" tag
|
||||||
|
Keywords []string `xml:"KEYWORD"` // keywords
|
||||||
|
}
|
||||||
|
|
||||||
|
// VCSound is the "pronunciation guide" property of the vCard.
|
||||||
|
type VCSound struct {
|
||||||
|
XMLName xml.Name `xml:"SOUND"` // must be a "SOUND" tag
|
||||||
|
Phonetic string `xml:"PHONETIC"` // phonetic pronunciation
|
||||||
|
BinaryValue string `xml:"BINVAL"` // binary audio value (Base64 encoded)
|
||||||
|
ExternalValue string `xml:"EXTVAL"` // external value of audio (URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VCClass is the "privacy classification" property of the vCard.
|
||||||
|
type VCClass struct {
|
||||||
|
XMLName xml.Name `xml:"CLASS"` // must be a "CLASS" tag
|
||||||
|
Public xml.Name `xml:"PUBLIC"` // presence indicates public information
|
||||||
|
Private xml.Name `xml:"PRIVATE"` // presence indicates private information
|
||||||
|
Confidential xml.Name `xml:"CONFIDENTIAL"` // presence indicates confidential information
|
||||||
|
}
|
||||||
|
|
||||||
|
// VCKey is the "authentication or encryption key" property of the vCard.
|
||||||
|
type VCKey struct {
|
||||||
|
XMLName xml.Name `xml:"KEY"` // must be a "KEY" tag
|
||||||
|
Type string `xml:"TYPE"` // type indicator
|
||||||
|
Credential string `xml:"CRED"` // credential value
|
||||||
|
}
|
||||||
|
|
||||||
|
func VCardFromContactInfo(ctx context.Context, target *VCard, ci *database.ContactInfo) error {
|
||||||
|
target.Version = "2.0"
|
||||||
|
target.FullName = ci.FullName(false)
|
||||||
|
target.Name.Family = util.IIF(ci.FamilyName != nil, *ci.FamilyName, "")
|
||||||
|
target.Name.Given = util.IIF(ci.GivenName != nil, *ci.GivenName, "")
|
||||||
|
target.Name.Middle = util.IIF(ci.MiddleInit != nil, *ci.MiddleInit+".", "")
|
||||||
|
target.Name.Prefix = util.IIF(ci.Prefix != nil, *ci.Prefix, "")
|
||||||
|
target.Name.Suffix = util.IIF(ci.Suffix != nil, *ci.Suffix, "")
|
||||||
|
target.URL = util.IIF(ci.URL != nil, *ci.URL, "")
|
||||||
|
if ci.LastUpdate != nil {
|
||||||
|
target.LastUpdate = ci.LastUpdate.Format(ISO8601)
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := make([]VCAddress, 1)
|
||||||
|
addr[0].Home.Local = "HOME"
|
||||||
|
addr[0].Postal.Local = "POSTAL"
|
||||||
|
addr[0].Preferred.Local = "PREF"
|
||||||
|
addr[0].Street = util.IIF(ci.Addr1 != nil, *ci.Addr1, "")
|
||||||
|
addr[0].ExtAddr = util.IIF(ci.Addr2 != nil, *ci.Addr2, "")
|
||||||
|
addr[0].Locality = util.IIF(ci.Locality != nil, *ci.Locality, "")
|
||||||
|
addr[0].Region = util.IIF(ci.Region != nil, *ci.Region, "")
|
||||||
|
addr[0].PCode = util.IIF(ci.PostalCode != nil, *ci.PostalCode, "")
|
||||||
|
addr[0].Country = util.IIF(ci.Country != nil, *ci.Country, "")
|
||||||
|
target.Address = &addr
|
||||||
|
|
||||||
|
phcount := util.IIF(ci.Phone != nil, 1, 0) + util.IIF(ci.Fax != nil, 1, 0) + util.IIF(ci.Mobile != nil, 1, 0)
|
||||||
|
if phcount > 0 {
|
||||||
|
phone := make([]VCTelephone, phcount)
|
||||||
|
i := 0
|
||||||
|
if ci.Phone != nil {
|
||||||
|
phone[i].Home.Local = "HOME"
|
||||||
|
phone[i].Voice.Local = "VOICE"
|
||||||
|
phone[i].Preferred.Local = "PREF"
|
||||||
|
phone[i].Number = *ci.Phone
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if ci.Fax != nil {
|
||||||
|
phone[i].Home.Local = "HOME"
|
||||||
|
phone[i].Fax.Local = "FAX"
|
||||||
|
phone[i].Preferred.Local = "PREF"
|
||||||
|
phone[i].Number = *ci.Fax
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if ci.Mobile != nil {
|
||||||
|
phone[i].Home.Local = "HOME"
|
||||||
|
phone[i].Cell.Local = "CELL"
|
||||||
|
phone[i].Voice.Local = "VOICE"
|
||||||
|
phone[i].Preferred.Local = "PREF"
|
||||||
|
phone[i].Number = *ci.Mobile
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i == phcount {
|
||||||
|
target.Tel = &phone
|
||||||
|
} else {
|
||||||
|
return errors.New("internal error in phone array")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ci.Email != nil {
|
||||||
|
email := make([]VCEmail, 1)
|
||||||
|
email[0].Home.Local = "HOME"
|
||||||
|
email[0].Internet.Local = "INTERNET"
|
||||||
|
email[0].Preferred.Local = "PREF"
|
||||||
|
email[0].UserID = *ci.Email
|
||||||
|
target.Email = &email
|
||||||
|
}
|
||||||
|
|
||||||
|
if ci.Company != nil {
|
||||||
|
var org VCOrganization
|
||||||
|
org.OrgName = *ci.Company
|
||||||
|
target.Org = &org
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -25,8 +25,12 @@ import (
|
|||||||
* Amsterdam uses this name for the format for backwards compatibility.
|
* Amsterdam uses this name for the format for backwards compatibility.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// ISO8601 is the full ISO 8601 formatting string.
|
||||||
const ISO8601 = "20060102T150405"
|
const ISO8601 = "20060102T150405"
|
||||||
|
|
||||||
|
// ISO8601_DATE is the ISO 8601 date-only formatting string.
|
||||||
|
const ISO8601_DATE = "20060102"
|
||||||
|
|
||||||
// VCIFBase is the top-level element for a VCIF file.
|
// VCIFBase is the top-level element for a VCIF file.
|
||||||
type VCIFBase struct {
|
type VCIFBase struct {
|
||||||
XMLName xml.Name `xml:"vcif"` // I am the <vcif> element
|
XMLName xml.Name `xml:"vcif"` // I am the <vcif> element
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* Amsterdam Web Communities System
|
||||||
|
* Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
// Package exports contains interfacing code for external data formats.
|
||||||
|
package exports
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
|
||||||
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
|
"git.erbosoft.com/amy/amsterdam/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file contains structures for working with Venice-Import-Users (VIU) XML files.
|
||||||
|
* Amsterdam uses this name for them for backward compatibility.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// VIUBase is the top level structure of the Venice-Import-Users format.
|
||||||
|
type VIUBase struct {
|
||||||
|
XMLName xml.Name `xml:"venice-import-users"` // must be a <venice-import-users> tag
|
||||||
|
Users []VIUUser `xml:"venice-user"` // the list of users
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIUUser is the structure representing a single user.
|
||||||
|
type VIUUser struct {
|
||||||
|
XMLName xml.Name `xml:"venice-user"` // must be a <venice-user> tag
|
||||||
|
ID int `xml:"id,attr"` // the UID for the user
|
||||||
|
Username string `xml:"username"` // user name
|
||||||
|
Password VIUPassword `xml:"password"` // password information
|
||||||
|
PasswordReminder string `xml:"password-reminder"` // password reminder string
|
||||||
|
Description string `xml:"description"` // description string
|
||||||
|
Options VIUOptions `xml:"options"` // user options
|
||||||
|
VCard VCard `xml:"vcard-temp vCard"` // user contact info in vCard XML format
|
||||||
|
Joins []VIUCommunityJoin `xml:"join"` // joined communities
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIUPassword represents the user password information.
|
||||||
|
type VIUPassword struct {
|
||||||
|
XMLName xml.Name `xml:"password"` // must be a <password> tag
|
||||||
|
Prehashed bool `xml:"prehashed,attr"` // has this password been prehashed?
|
||||||
|
Hash string `xml:",chardata"` // password information
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIUOptions represents the user options.
|
||||||
|
type VIUOptions struct {
|
||||||
|
XMLName xml.Name `xml:"options"` // must be an <options> tag
|
||||||
|
Confirmed bool `xml:"confirmed,attr"` // E-mail address confirmed?
|
||||||
|
Locked bool `xml:"locked,attr"` // user account locked?
|
||||||
|
Role string `xml:"role,attr"` // user's base role
|
||||||
|
HideAddr bool `xml:"hideaddr,attr"` // hide address?
|
||||||
|
HidePhone bool `xml:"hidephone,attr"` // hide phone number?
|
||||||
|
HideFax bool `xml:"hidefax,attr"` // hide fax number?
|
||||||
|
HideEmail bool `xml:"hideemail,attr"` // hide E-mail address?
|
||||||
|
AutoJoin bool `xml:"autojoin,attr"` // auto-join communities?
|
||||||
|
PostPictures bool `xml:"postpictures,attr"` // show pictures in posts?
|
||||||
|
OptOut bool `xml:"optout,attr"` // opt out of mass E-mail?
|
||||||
|
NoPhoto bool `xml:"nophoto,attr"` // disallow photo uploads?
|
||||||
|
Locale string `xml:"locale,attr"` // user locale
|
||||||
|
ZoneHint string `xml:"zonehint,attr"` // user timezone hint
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIUCommunityJoin represnts all the communities the user has joined.
|
||||||
|
type VIUCommunityJoin struct {
|
||||||
|
XMLName xml.Name `xml:"join"` // must be a <join> tag
|
||||||
|
Role string `xml:"role,attr"` // role we have in the community
|
||||||
|
Community string `xml:",chardata"` // name of community joined
|
||||||
|
}
|
||||||
|
|
||||||
|
func VIUUserFromUser(ctx context.Context, target *VIUUser, u *database.User) error {
|
||||||
|
// Fill base fields first.
|
||||||
|
target.ID = int(u.Uid)
|
||||||
|
target.Username = u.Username
|
||||||
|
target.Password.Prehashed = true
|
||||||
|
target.Password.Hash = u.Passhash
|
||||||
|
target.PasswordReminder = u.PassReminder
|
||||||
|
target.Description = util.IIF(u.Description != nil, *u.Description, "")
|
||||||
|
|
||||||
|
// Get the contact info.
|
||||||
|
ci, err := u.ContactInfo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the contact info into the VCard.
|
||||||
|
err = VCardFromContactInfo(ctx, &(target.VCard), ci)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill extra fields into the VCard.
|
||||||
|
if u.DOB != nil {
|
||||||
|
target.VCard.BDay = u.DOB.Format(ISO8601_DATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in the Options structure from what we have.
|
||||||
|
target.Options.Confirmed = u.VerifyEMail
|
||||||
|
target.Options.Locked = u.Lockout
|
||||||
|
target.Options.Role = database.AmRoleList("Global.AllUserLevels").FindForLevel(u.BaseLevel).ID()
|
||||||
|
target.Options.HideAddr = ci.PrivateAddr
|
||||||
|
target.Options.HidePhone = ci.PrivatePhone
|
||||||
|
target.Options.HideFax = ci.PrivateFax
|
||||||
|
target.Options.HideEmail = ci.PrivateEmail
|
||||||
|
|
||||||
|
// Load user preferences.
|
||||||
|
prefs, err := u.Prefs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in from user preferences.
|
||||||
|
target.Options.Locale = prefs.LocaleID
|
||||||
|
target.Options.ZoneHint = prefs.TimeZoneID
|
||||||
|
target.VCard.TZ = prefs.LocationISO8601Offset()
|
||||||
|
|
||||||
|
// Load user flags.
|
||||||
|
flags, err := u.Flags(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
target.Options.PostPictures = flags.Get(database.UserFlagPicturesInPosts)
|
||||||
|
target.Options.OptOut = flags.Get(database.UserFlagMassMailOptOut)
|
||||||
|
target.Options.NoPhoto = flags.Get(database.UserFlagDisallowSetPhoto)
|
||||||
|
|
||||||
|
// TODO - fill in Autojoin option and Joins
|
||||||
|
/*
|
||||||
|
AutoJoin bool `xml:"autojoin,attr"` // auto-join communities?
|
||||||
|
*/
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -180,3 +180,12 @@ func MyIPAddress() (net.IP, error) {
|
|||||||
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
||||||
return localAddr.IP, nil
|
return localAddr.IP, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IIF is an "immediate-if" function returning its second argument if the first one is true, the third one if not.
|
||||||
|
func IIF[A any](expr bool, v1, v2 A) A {
|
||||||
|
if expr {
|
||||||
|
return v1
|
||||||
|
} else {
|
||||||
|
return v2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user