invite link gen implemented

This commit is contained in:
r8zavetr8v 2024-05-26 03:30:35 +03:00
parent 2ab2cf61ed
commit a97a2cdf19
17 changed files with 281 additions and 631 deletions

View File

@ -16,4 +16,4 @@ RUN go build -ldflags="-s -w" -o /app/blockd cmd/main.go
EXPOSE 8080
CMD ["/app/blockd", "-log-level=debug","-log-local=false","-log-add-source=true","-rest-address=0.0.0.0:8080","-db-host=blockd-db:5432","-db-database=blockd","-db-user=blockd","-db-secret=blockd","-db-enable-tls=false"]
CMD ["/app/blockd", "-log-level=info","-log-local=false","-log-add-source=true","-rest-address=0.0.0.0:8080","-db-host=blockd-db:5432","-db-database=blockd","-db-user=blockd","-db-secret=blockd","-db-enable-tls=false", "-cache-host=blockd-cache:6379"]

View File

@ -2,7 +2,6 @@ package main
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
@ -117,8 +116,6 @@ func main() {
},
}
fmt.Println(config)
service, cleanup, err := factory.ProvideService(config)
if err != nil {
panic(err)

View File

@ -17,15 +17,13 @@ services:
context: .
dockerfile: ./Dockerfile
ports:
- 8080:8080
- 8081:8080
networks:
- blockd-net
- syslog
depends_on:
blockd-db:
condition: service_healthy
syslog:
condition: service_started
profiles: [blockd]
blockd-db:
@ -61,52 +59,53 @@ services:
- 6379:6379
profiles: [blockd, database, noback]
# prometheus:
# image: prom/prometheus
# container_name: prometheus
# command:
# - '--config.file=/etc/prometheus/prometheus.yml'
# ports:
# - 9091:9090
# restart: unless-stopped
# networks:
# - blockd-net
# volumes:
# - ./prometheus:/etc/prometheus
# - prometheus_data:/prometheus
# profiles: [blockd, metrics, noback]
prometheus:
image: prom/prometheus
container_name: prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
ports:
- 9091:9090
restart: unless-stopped
networks:
- blockd-net
volumes:
- ./prometheus:/etc/prometheus
- prometheus_data:/prometheus
profiles: [metrics]
# grafana:
# image: grafana/grafana
# container_name: grafana
# ports:
# - 3112:3000
# restart: unless-stopped
# networks:
# - blockd-net
# environment:
# - GF_SECURITY_ADMIN_USER=admin
# - GF_SECURITY_ADMIN_PASSWORD=grafana
# volumes:
# - ./grafana:/etc/grafana/provisioning/datasources
# profiles: [blockd, metrics, noback]
grafana:
image: grafana/grafana
container_name: grafana
ports:
- 3112:3000
restart: unless-stopped
networks:
- blockd-net
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=grafana
volumes:
- ./grafana:/etc/grafana/provisioning/datasources
profiles: [metrics]
# syslog:
# image: linuxserver/syslog-ng:3.36.1
# container_name: syslog-ng
# environment:
# - PUID=0
# - PGID=0
# - TZ=UTC
# volumes:
# - /srv/syslog/config:/config
# - /srv/syslog/logs:/var/log
# ports:
# - 514:5514/udp
# - 601:6601/tcp
# - 6514:6514/tcp
# restart: unless-stopped
# networks:
# - syslog
# logging:
# driver: "json-file"
syslog:
image: linuxserver/syslog-ng:3.36.1
container_name: syslog-ng
environment:
- PUID=0
- PGID=0
- TZ=UTC
volumes:
- /srv/syslog/config:/config
- /srv/syslog/logs:/var/log
ports:
- 514:5514/udp
- 601:6601/tcp
- 6514:6514/tcp
restart: unless-stopped
networks:
- syslog
logging:
driver: "json-file"
profiles: [metrics]

View File

@ -16,6 +16,7 @@ import (
"github.com/emochka2007/block-accounting/internal/usecase/interactors/organizations"
"github.com/emochka2007/block-accounting/internal/usecase/interactors/transactions"
"github.com/emochka2007/block-accounting/internal/usecase/interactors/users"
"github.com/emochka2007/block-accounting/internal/usecase/repository/auth"
)
var interfaceSet wire.ProviderSet = wire.NewSet(
@ -67,12 +68,14 @@ func provideAuthController(
usersInteractor users.UsersInteractor,
authPresenter presenters.AuthPresenter,
jwtInteractor jwt.JWTInteractor,
repo auth.Repository,
) controllers.AuthController {
return controllers.NewAuthController(
log.WithGroup("auth-controller"),
authPresenter,
usersInteractor,
jwtInteractor,
repo,
)
}

View File

@ -27,7 +27,7 @@ func ProvideService(c config.Config) (service.Service, func(), error) {
authRepository := provideAuthRepository(db)
jwtInteractor := provideJWTInteractor(c, usersInteractor, authRepository)
authPresenter := provideAuthPresenter(jwtInteractor)
authController := provideAuthController(logger, usersInteractor, authPresenter, jwtInteractor)
authController := provideAuthController(logger, usersInteractor, authPresenter, jwtInteractor, authRepository)
organizationsRepository := provideOrganizationsRepository(db, usersRepository)
client, cleanup2 := provideRedisConnection(c)
cache := provideRedisCache(client, logger)

View File

@ -2,18 +2,23 @@ package controllers
import (
"context"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"log/slog"
"net/http"
"strings"
"time"
"github.com/emochka2007/block-accounting/internal/interface/rest/domain"
"github.com/emochka2007/block-accounting/internal/interface/rest/presenters"
"github.com/emochka2007/block-accounting/internal/pkg/bip39"
"github.com/emochka2007/block-accounting/internal/pkg/ctxmeta"
"github.com/emochka2007/block-accounting/internal/pkg/hdwallet"
"github.com/emochka2007/block-accounting/internal/usecase/interactors/jwt"
"github.com/emochka2007/block-accounting/internal/usecase/interactors/users"
"github.com/emochka2007/block-accounting/internal/usecase/repository/auth"
)
var (
@ -34,6 +39,7 @@ type authController struct {
presenter presenters.AuthPresenter
usersInteractor users.UsersInteractor
jwtInteractor jwt.JWTInteractor
repo auth.Repository
}
func NewAuthController(
@ -41,12 +47,14 @@ func NewAuthController(
presenter presenters.AuthPresenter,
usersInteractor users.UsersInteractor,
jwtInteractor jwt.JWTInteractor,
repo auth.Repository,
) AuthController {
return &authController{
log: log,
presenter: presenter,
usersInteractor: usersInteractor,
jwtInteractor: jwtInteractor,
repo: repo,
}
}
@ -140,9 +148,64 @@ func (c *authController) Refresh(w http.ResponseWriter, req *http.Request) ([]by
// const mnemonicEntropyBitSize int = 256
func (c *authController) Invite(w http.ResponseWriter, req *http.Request) ([]byte, error) {
func (c *authController) Invite(w http.ResponseWriter, r *http.Request) ([]byte, error) {
request, err := presenters.CreateRequest[domain.NewInviteLinkRequest](r)
if err != nil {
return nil, fmt.Errorf("error create refresh request. %w", err)
}
return nil, nil
organizationID, err := ctxmeta.OrganizationId(r.Context())
if err != nil {
return nil, fmt.Errorf("error fetch organization id from context. %w", err)
}
user, err := ctxmeta.User(r.Context())
if err != nil {
return nil, fmt.Errorf("error fetch user from context. %w", err)
}
c.log.Debug(
"invite request",
slog.Int("exp at", request.ExpirationDate),
slog.String("org id", organizationID.String()),
slog.String("inviter id", user.Id().String()),
)
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()
linkHash := sha256.New()
linkHash.Write([]byte(
user.Id().String() + organizationID.String() + time.Now().String(),
))
linkHashString := strings.ReplaceAll(
strings.ReplaceAll(
strings.ReplaceAll(
base64.StdEncoding.EncodeToString(linkHash.Sum(nil)),
"/", "%",
),
"?", "@",
),
"&", "#",
)
c.log.Debug(
"",
slog.String("link", linkHashString),
)
if err := c.repo.AddInvite(ctx, auth.AddInviteParams{
LinkHash: linkHashString,
CreatedBy: *user,
CreatedAt: time.Now(),
ExpiredAt: time.UnixMilli(int64(request.ExpirationDate)),
}); err != nil {
return nil, fmt.Errorf("error add new invite link. %w", err)
}
return c.presenter.ResponseNewInvite(ctx, organizationID, linkHashString)
}
func (c *authController) JoinWithInvite(w http.ResponseWriter, req *http.Request) ([]byte, error) {

View File

@ -112,7 +112,11 @@ type NewMultisigRequest struct {
Owners []struct {
PublicKey string `json:"public_key"`
} `json:"owners"`
Confirmations int `json:confirmations`
Confirmations int `json:"confirmations"`
}
type NewInviteLinkRequest struct {
ExpirationDate int `json:"expiration_date"`
}
func BuildRequest[T any](data []byte) (*T, error) {

View File

@ -1,6 +1,7 @@
package presenters
import (
"context"
"encoding/json"
"fmt"
"time"
@ -8,12 +9,18 @@ import (
"github.com/emochka2007/block-accounting/internal/interface/rest/domain"
"github.com/emochka2007/block-accounting/internal/pkg/models"
"github.com/emochka2007/block-accounting/internal/usecase/interactors/jwt"
"github.com/google/uuid"
)
type AuthPresenter interface {
ResponseJoin(user *models.User) ([]byte, error)
ResponseLogin(user *models.User) ([]byte, error)
ResponseRefresh(tokens jwt.AccessToken) ([]byte, error)
ResponseNewInvite(
ctx context.Context,
organizationID uuid.UUID,
link string,
) ([]byte, error)
}
type authPresenter struct {
@ -79,3 +86,18 @@ func (p *authPresenter) ResponseRefresh(tokens jwt.AccessToken) ([]byte, error)
return out, nil
}
func (p *authPresenter) ResponseNewInvite(
ctx context.Context,
organizationID uuid.UUID,
link string,
) ([]byte, error) {
out, err := json.Marshal(map[string]string{
"link": "/" + organizationID.String() + "/invite/" + link,
})
if err != nil {
return nil, fmt.Errorf("error marshal refresh response. %w", err)
}
return out, nil
}

View File

@ -12,6 +12,8 @@ type Multisig struct {
OrganizationID uuid.UUID
Owners []OrganizationParticipant
ConfirmationsRequired int
CreatedAt time.Time
UpdatedAt time.Time
}
type MultisigConfirmation struct {

View File

@ -0,0 +1,7 @@
package auth
import "errors"
var (
ErrorInviteLinkExpired = errors.New("invite link expired")
)

View File

@ -8,6 +8,7 @@ import (
"time"
sq "github.com/Masterminds/squirrel"
"github.com/emochka2007/block-accounting/internal/pkg/models"
sqltools "github.com/emochka2007/block-accounting/internal/pkg/sqlutils"
"github.com/google/uuid"
)
@ -60,6 +61,9 @@ type Repository interface {
AddToken(ctx context.Context, params AddTokenParams) error
GetTokens(ctx context.Context, params GetTokenParams) (*AccessToken, error)
RefreshToken(ctx context.Context, params RefreshTokenParams) error
AddInvite(ctx context.Context, params AddInviteParams) error
MarkAsUsedLink(ctx context.Context, linkHash string, usedAt time.Time) error
}
type repositorySQL struct {
@ -180,6 +184,70 @@ func (r *repositorySQL) GetTokens(ctx context.Context, params GetTokenParams) (*
return token, nil
}
type AddInviteParams struct {
LinkHash string
CreatedBy models.User
CreatedAt time.Time
ExpiredAt time.Time
}
func (r *repositorySQL) AddInvite(
ctx context.Context,
params AddInviteParams,
) error {
return sqltools.Transaction(ctx, r.db, func(ctx context.Context) error {
query := sq.Insert("invites").Columns(
"link_hash",
"created_by",
"created_at",
"expired_at",
).Values(
params.LinkHash,
params.CreatedBy.Id(),
params.CreatedAt,
params.ExpiredAt,
).PlaceholderFormat(sq.Dollar)
if _, err := query.RunWith(r.Conn(ctx)).ExecContext(ctx); err != nil {
return fmt.Errorf("error add invite link. %w", err)
}
return nil
})
}
func (r *repositorySQL) MarkAsUsedLink(
ctx context.Context,
linkHash string,
usedAt time.Time,
) error {
return sqltools.Transaction(ctx, r.db, func(ctx context.Context) error {
query := sq.Select("expired_at").From("invites").Where(sq.Eq{
"link_hash": linkHash,
}).Limit(1).PlaceholderFormat(sq.Dollar)
var expAt time.Time
if err := query.RunWith(r.Conn(ctx)).QueryRowContext(ctx).Scan(&expAt); err != nil {
return fmt.Errorf("error fetch expiration date from database. %w", err)
}
if expAt.After(time.Now()) {
return ErrorInviteLinkExpired
}
updateQuery := sq.Update("invites").SetMap(sq.Eq{
"used_at": usedAt,
})
if _, err := updateQuery.RunWith(r.Conn(ctx)).ExecContext(ctx); err != nil {
return fmt.Errorf("error add invite link. %w", err)
}
return nil
})
}
func NewRepository(db *sql.DB) Repository {
return &repositorySQL{
db: db,

View File

@ -20,8 +20,6 @@ func ProvideDatabaseConnection(c config.Config) (*sql.DB, func(), error) {
c.DB.User, c.DB.Secret, c.DB.Host, c.DB.Database, sslmode,
)
fmt.Println(connStr)
db, err := sql.Open("postgres", connStr)
if err != nil {
return nil, func() {}, fmt.Errorf("error connecting to database: %w", err)

View File

@ -475,8 +475,6 @@ func (r *repositorySQL) Participants(
})
}
fmt.Println(query.ToSql())
rows, err := query.RunWith(r.Conn(ctx)).QueryContext(ctx)
if err != nil {
return fmt.Errorf("error fetch employees from database. %w", err)
@ -740,8 +738,6 @@ func (r *repositorySQL) fetchEmployees(
})
}
fmt.Println(query.ToSql())
rows, err := query.RunWith(r.Conn(ctx)).QueryContext(ctx)
if err != nil {
return fmt.Errorf("error fetch employees from database. %w", err)
@ -808,7 +804,7 @@ func (r *repositorySQL) AddEmployee(ctx context.Context, employee models.Employe
employee.WalletAddress,
employee.CreatedAt,
employee.UpdatedAt,
)
).PlaceholderFormat(sq.Dollar)
if _, err := query.RunWith(r.Conn(ctx)).ExecContext(ctx); err != nil {
return fmt.Errorf("error add employee. %w", err)

View File

@ -50,6 +50,8 @@ type Repository interface {
ConfirmTransaction(ctx context.Context, params ConfirmTransactionParams) error
CancelTransaction(ctx context.Context, params CancelTransactionParams) error
AddMultisig(ctx context.Context, multisig models.Multisig) error
}
type repositorySQL struct {
@ -89,8 +91,6 @@ func (r *repositorySQL) GetTransactions(
}
}()
fmt.Println(query.ToSql())
for rows.Next() {
var (
id uuid.UUID
@ -400,3 +400,48 @@ func buildGetTransactionsQuery(params GetTransactionsParams) sq.SelectBuilder {
return query
}
func (r *repositorySQL) AddMultisig(
ctx context.Context,
multisig models.Multisig,
) error {
return sqltools.Transaction(ctx, r.db, func(ctx context.Context) error {
query := sq.Insert("multisigs").Columns(
"id",
"organization_id",
"title",
"created_at",
"updated_at",
).Values(
multisig.ID,
multisig.OrganizationID,
multisig.Title,
multisig.CreatedAt,
multisig.UpdatedAt,
).PlaceholderFormat(sq.Dollar)
if _, err := query.RunWith(r.Conn(ctx)).ExecContext(ctx); err != nil {
return fmt.Errorf("error insert multisig data. %w", err)
}
for _, owner := range multisig.Owners {
addOwnerQuery := sq.Insert("multisig_owners").Columns(
"multisig_id",
"owner_id",
"created_at",
"updated_at",
).Values(
multisig.ID,
owner.Id(),
multisig.CreatedAt,
multisig.UpdatedAt,
)
if _, err := addOwnerQuery.RunWith(r.Conn(ctx)).ExecContext(ctx); err != nil {
return fmt.Errorf("error insert multisig owner data. %w", err)
}
}
return nil
})
}

View File

@ -72,8 +72,6 @@ func (r *repositorySQL) Get(ctx context.Context, params GetParams) ([]*models.Us
query = query.Where("u.seed = ?", params.Seed)
}
fmt.Println(query.ToSql())
rows, err := query.RunWith(r.Conn(ctx)).QueryContext(ctx)
if err != nil {
return fmt.Errorf("error fetch data from database. %w", err)

View File

@ -149,16 +149,18 @@ create table contracts (
updated_at timestamp default current_timestamp
);
create intex if not exists idx_contracts_organization_id_multisig
create index if not exists idx_contracts_organization_id_multisig
on contracts (organization_id, multisig);
create intex if not exists idx_contracts_organization_id_created_by
create index if not exists idx_contracts_organization_id_created_by
on contracts (organization_id, created_by);
create table multisigs (
id uuid primary key,
organization_id uuid not null references organizations(id),
title varchar(350) default 'New Multi-Sig'
title varchar(350) default 'New Multi-Sig',
created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp
);
create table multisig_owners (
@ -169,10 +171,10 @@ create table multisig_owners (
primary key (multisig_id, owner_id)
);
create intex if not exists idx_multisig_owners_multisig_id
create index if not exists idx_multisig_owners_multisig_id
on multisig_owners (multisig_id);
create intex if not exists idx_multisig_owners_owner_id
create index if not exists idx_multisig_owners_owner_id
on multisig_owners (owner_id);
create table multisig_confirmations (
@ -182,14 +184,14 @@ create table multisig_confirmations (
primary key (multisig_id, owner_id)
);
create intex if not exists idx_multisig_confirmations_owners_multisig_id
create index if not exists idx_multisig_confirmations_owners_multisig_id
on multisig_confirmations (multisig_id);
create intex if not exists idx_multisig_confirmations_owners_owner_id
create index if not exists idx_multisig_confirmations_owners_owner_id
on multisig_confirmations (owner_id);
create table invites (
link_hash bytea primary key,
link_hash varchar(64) primary key,
created_by uuid not null references users(id),
created_at timestamp default current_timestamp,
expired_at timestamp default null,

View File

@ -1,554 +0,0 @@
<Valuta name="Foreign Currency Market Lib">
<Item ID="R01010">
<Name>Австралийский доллар</Name>
<EngName>Australian Dollar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01010 </ParentCode>
<ISO_Num_Code>36</ISO_Num_Code>
<ISO_Char_Code>AUD</ISO_Char_Code>
</Item>
<Item ID="R01015">
<Name>Австрийский шиллинг</Name>
<EngName>Austrian Shilling</EngName>
<Nominal>1000</Nominal>
<ParentCode>R01015 </ParentCode>
<ISO_Num_Code>40</ISO_Num_Code>
<ISO_Char_Code>ATS</ISO_Char_Code>
</Item>
<Item ID="R01020A">
<Name>Азербайджанский манат</Name>
<EngName>Azerbaijan Manat</EngName>
<Nominal>1</Nominal>
<ParentCode>R01020 </ParentCode>
<ISO_Num_Code>944</ISO_Num_Code>
<ISO_Char_Code>AZN</ISO_Char_Code>
</Item>
<Item ID="R01035">
<Name>Фунт стерлингов Соединенного королевства</Name>
<EngName>British Pound Sterling</EngName>
<Nominal>1</Nominal>
<ParentCode>R01035 </ParentCode>
<ISO_Num_Code>826</ISO_Num_Code>
<ISO_Char_Code>GBP</ISO_Char_Code>
</Item>
<Item ID="R01040F">
<Name>Ангольская новая кванза</Name>
<EngName>Angolan new Kwanza</EngName>
<Nominal>100000</Nominal>
<ParentCode>R01040 </ParentCode>
<ISO_Num_Code>24</ISO_Num_Code>
<ISO_Char_Code>AON</ISO_Char_Code>
</Item>
<Item ID="R01060">
<Name>Армянский драм</Name>
<EngName>Armenia Dram</EngName>
<Nominal>100</Nominal>
<ParentCode>R01060 </ParentCode>
<ISO_Num_Code>51</ISO_Num_Code>
<ISO_Char_Code>AMD</ISO_Char_Code>
</Item>
<Item ID="R01090B">
<Name>Белорусский рубль</Name>
<EngName>Belarussian Ruble</EngName>
<Nominal>1</Nominal>
<ParentCode>R01090 </ParentCode>
<ISO_Num_Code>933</ISO_Num_Code>
<ISO_Char_Code>BYN</ISO_Char_Code>
</Item>
<Item ID="R01095">
<Name>Бельгийский франк</Name>
<EngName>Belgium Franc</EngName>
<Nominal>1000</Nominal>
<ParentCode>R01095 </ParentCode>
<ISO_Num_Code>56</ISO_Num_Code>
<ISO_Char_Code>BEF</ISO_Char_Code>
</Item>
<Item ID="R01100">
<Name>Болгарский лев</Name>
<EngName>Bulgarian lev</EngName>
<Nominal>1</Nominal>
<ParentCode>R01100 </ParentCode>
<ISO_Num_Code>975</ISO_Num_Code>
<ISO_Char_Code>BGN</ISO_Char_Code>
</Item>
<Item ID="R01115">
<Name>Бразильский реал</Name>
<EngName>Brazil Real</EngName>
<Nominal>1</Nominal>
<ParentCode>R01115 </ParentCode>
<ISO_Num_Code>986</ISO_Num_Code>
<ISO_Char_Code>BRL</ISO_Char_Code>
</Item>
<Item ID="R01135">
<Name>Венгерский форинт</Name>
<EngName>Hungarian Forint</EngName>
<Nominal>100</Nominal>
<ParentCode>R01135 </ParentCode>
<ISO_Num_Code>348</ISO_Num_Code>
<ISO_Char_Code>HUF</ISO_Char_Code>
</Item>
<Item ID="R01150">
<Name>Вьетнамский донг</Name>
<EngName>Vietnam Dong</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01150 </ParentCode>
<ISO_Num_Code>704</ISO_Num_Code>
<ISO_Char_Code>VND</ISO_Char_Code>
</Item>
<Item ID="R01200">
<Name>Гонконгский доллар</Name>
<EngName>Hong Kong Dollar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01200 </ParentCode>
<ISO_Num_Code>344</ISO_Num_Code>
<ISO_Char_Code>HKD</ISO_Char_Code>
</Item>
<Item ID="R01205">
<Name>Греческая драхма</Name>
<EngName>Greek Drachma</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01205 </ParentCode>
<ISO_Num_Code>300</ISO_Num_Code>
<ISO_Char_Code>GRD</ISO_Char_Code>
</Item>
<Item ID="R01210">
<Name>Грузинский лари</Name>
<EngName>Georgia Lari</EngName>
<Nominal>1</Nominal>
<ParentCode>R01210 </ParentCode>
<ISO_Num_Code>981</ISO_Num_Code>
<ISO_Char_Code>GEL</ISO_Char_Code>
</Item>
<Item ID="R01215">
<Name>Датская крона</Name>
<EngName>Danish Krone</EngName>
<Nominal>1</Nominal>
<ParentCode>R01215 </ParentCode>
<ISO_Num_Code>208</ISO_Num_Code>
<ISO_Char_Code>DKK</ISO_Char_Code>
</Item>
<Item ID="R01230">
<Name>Дирхам ОАЭ</Name>
<EngName>UAE Dirham</EngName>
<Nominal>1</Nominal>
<ParentCode>R01230 </ParentCode>
<ISO_Num_Code>784</ISO_Num_Code>
<ISO_Char_Code>AED</ISO_Char_Code>
</Item>
<Item ID="R01235">
<Name>Доллар США</Name>
<EngName>US Dollar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01235 </ParentCode>
<ISO_Num_Code>840</ISO_Num_Code>
<ISO_Char_Code>USD</ISO_Char_Code>
</Item>
<Item ID="R01239">
<Name>Евро</Name>
<EngName>Euro</EngName>
<Nominal>1</Nominal>
<ParentCode>R01239 </ParentCode>
<ISO_Num_Code>978</ISO_Num_Code>
<ISO_Char_Code>EUR</ISO_Char_Code>
</Item>
<Item ID="R01240">
<Name>Египетский фунт</Name>
<EngName>Egyptian Pound</EngName>
<Nominal>10</Nominal>
<ParentCode>R01240 </ParentCode>
<ISO_Num_Code>818</ISO_Num_Code>
<ISO_Char_Code>EGP</ISO_Char_Code>
</Item>
<Item ID="R01270">
<Name>Индийская рупия</Name>
<EngName>Indian Rupee</EngName>
<Nominal>10</Nominal>
<ParentCode>R01270 </ParentCode>
<ISO_Num_Code>356</ISO_Num_Code>
<ISO_Char_Code>INR</ISO_Char_Code>
</Item>
<Item ID="R01280">
<Name>Индонезийская рупия</Name>
<EngName>Indonesian Rupiah</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01280 </ParentCode>
<ISO_Num_Code>360</ISO_Num_Code>
<ISO_Char_Code>IDR</ISO_Char_Code>
</Item>
<Item ID="R01305">
<Name>Ирландский фунт</Name>
<EngName>Irish Pound</EngName>
<Nominal>100</Nominal>
<ParentCode>R01305 </ParentCode>
<ISO_Num_Code>372</ISO_Num_Code>
<ISO_Char_Code>IEP</ISO_Char_Code>
</Item>
<Item ID="R01310">
<Name>Исландская крона</Name>
<EngName>Iceland Krona</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01310 </ParentCode>
<ISO_Num_Code>352</ISO_Num_Code>
<ISO_Char_Code>ISK</ISO_Char_Code>
</Item>
<Item ID="R01315">
<Name>Испанская песета</Name>
<EngName>Spanish Peseta</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01315 </ParentCode>
<ISO_Num_Code>724</ISO_Num_Code>
<ISO_Char_Code>ESP</ISO_Char_Code>
</Item>
<Item ID="R01325">
<Name>Итальянская лира</Name>
<EngName>Italian Lira</EngName>
<Nominal>100000</Nominal>
<ParentCode>R01325 </ParentCode>
<ISO_Num_Code>380</ISO_Num_Code>
<ISO_Char_Code>ITL</ISO_Char_Code>
</Item>
<Item ID="R01335">
<Name>Казахстанский тенге</Name>
<EngName>Kazakhstan Tenge</EngName>
<Nominal>100</Nominal>
<ParentCode>R01335 </ParentCode>
<ISO_Num_Code>398</ISO_Num_Code>
<ISO_Char_Code>KZT</ISO_Char_Code>
</Item>
<Item ID="R01350">
<Name>Канадский доллар</Name>
<EngName>Canadian Dollar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01350 </ParentCode>
<ISO_Num_Code>124</ISO_Num_Code>
<ISO_Char_Code>CAD</ISO_Char_Code>
</Item>
<Item ID="R01355">
<Name>Катарский риал</Name>
<EngName>Qatari Riyal</EngName>
<Nominal>1</Nominal>
<ParentCode>R01355 </ParentCode>
<ISO_Num_Code>634</ISO_Num_Code>
<ISO_Char_Code>QAR</ISO_Char_Code>
</Item>
<Item ID="R01370">
<Name>Киргизский сом</Name>
<EngName>Kyrgyzstan Som</EngName>
<Nominal>10</Nominal>
<ParentCode>R01370 </ParentCode>
<ISO_Num_Code>417</ISO_Num_Code>
<ISO_Char_Code>KGS</ISO_Char_Code>
</Item>
<Item ID="R01375">
<Name>Китайский юань</Name>
<EngName>China Yuan</EngName>
<Nominal>1</Nominal>
<ParentCode>R01375 </ParentCode>
<ISO_Num_Code>156</ISO_Num_Code>
<ISO_Char_Code>CNY</ISO_Char_Code>
</Item>
<Item ID="R01390">
<Name>Кувейтский динар</Name>
<EngName>Kuwaiti Dinar</EngName>
<Nominal>10</Nominal>
<ParentCode>R01390 </ParentCode>
<ISO_Num_Code>414</ISO_Num_Code>
<ISO_Char_Code>KWD</ISO_Char_Code>
</Item>
<Item ID="R01405">
<Name>Латвийский лат</Name>
<EngName>Latvian Lat</EngName>
<Nominal>1</Nominal>
<ParentCode>R01405 </ParentCode>
<ISO_Num_Code>428</ISO_Num_Code>
<ISO_Char_Code>LVL</ISO_Char_Code>
</Item>
<Item ID="R01420">
<Name>Ливанский фунт</Name>
<EngName>Lebanese Pound</EngName>
<Nominal>100000</Nominal>
<ParentCode>R01420 </ParentCode>
<ISO_Num_Code>422</ISO_Num_Code>
<ISO_Char_Code>LBP</ISO_Char_Code>
</Item>
<Item ID="R01435">
<Name>Литовский лит</Name>
<EngName>Lithuanian Lita</EngName>
<Nominal>1</Nominal>
<ParentCode>R01435 </ParentCode>
<ISO_Num_Code>440</ISO_Num_Code>
<ISO_Char_Code>LTL</ISO_Char_Code>
</Item>
<Item ID="R01436">
<Name>Литовский талон</Name>
<EngName>Lithuanian talon</EngName>
<Nominal>1</Nominal>
<ParentCode>R01435 </ParentCode>
<ISO_Num_Code/>
<ISO_Char_Code/>
</Item>
<Item ID="R01500">
<Name>Молдавский лей</Name>
<EngName>Moldova Lei</EngName>
<Nominal>10</Nominal>
<ParentCode>R01500 </ParentCode>
<ISO_Num_Code>498</ISO_Num_Code>
<ISO_Char_Code>MDL</ISO_Char_Code>
</Item>
<Item ID="R01510">
<Name>Немецкая марка</Name>
<EngName>Deutsche Mark</EngName>
<Nominal>1</Nominal>
<ParentCode>R01510 </ParentCode>
<ISO_Num_Code>276</ISO_Num_Code>
<ISO_Char_Code>DEM</ISO_Char_Code>
</Item>
<Item ID="R01510A">
<Name>Немецкая марка</Name>
<EngName>Deutsche Mark</EngName>
<Nominal>100</Nominal>
<ParentCode>R01510 </ParentCode>
<ISO_Num_Code>280</ISO_Num_Code>
<ISO_Char_Code>DEM</ISO_Char_Code>
</Item>
<Item ID="R01523">
<Name>Нидерландский гульден</Name>
<EngName>Netherlands Gulden</EngName>
<Nominal>100</Nominal>
<ParentCode>R01523 </ParentCode>
<ISO_Num_Code>528</ISO_Num_Code>
<ISO_Char_Code>NLG</ISO_Char_Code>
</Item>
<Item ID="R01530">
<Name>Новозеландский доллар</Name>
<EngName>New Zealand Dollar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01530 </ParentCode>
<ISO_Num_Code>554</ISO_Num_Code>
<ISO_Char_Code>NZD</ISO_Char_Code>
</Item>
<Item ID="R01535">
<Name>Норвежская крона</Name>
<EngName>Norwegian Krone</EngName>
<Nominal>10</Nominal>
<ParentCode>R01535 </ParentCode>
<ISO_Num_Code>578</ISO_Num_Code>
<ISO_Char_Code>NOK</ISO_Char_Code>
</Item>
<Item ID="R01565">
<Name>Польский злотый</Name>
<EngName>Polish Zloty</EngName>
<Nominal>1</Nominal>
<ParentCode>R01565 </ParentCode>
<ISO_Num_Code>985</ISO_Num_Code>
<ISO_Char_Code>PLN</ISO_Char_Code>
</Item>
<Item ID="R01570">
<Name>Португальский эскудо</Name>
<EngName>Portuguese Escudo</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01570 </ParentCode>
<ISO_Num_Code>620</ISO_Num_Code>
<ISO_Char_Code>PTE</ISO_Char_Code>
</Item>
<Item ID="R01585">
<Name>Румынский лей</Name>
<EngName>Romanian Leu</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01585 </ParentCode>
<ISO_Num_Code>642</ISO_Num_Code>
<ISO_Char_Code>ROL</ISO_Char_Code>
</Item>
<Item ID="R01585F">
<Name>Румынский лей</Name>
<EngName>Romanian Leu</EngName>
<Nominal>1</Nominal>
<ParentCode>R01585 </ParentCode>
<ISO_Num_Code>946</ISO_Num_Code>
<ISO_Char_Code>RON</ISO_Char_Code>
</Item>
<Item ID="R01589">
<Name>СДР (специальные права заимствования)</Name>
<EngName>SDR</EngName>
<Nominal>1</Nominal>
<ParentCode>R01589 </ParentCode>
<ISO_Num_Code>960</ISO_Num_Code>
<ISO_Char_Code>XDR</ISO_Char_Code>
</Item>
<Item ID="R01625">
<Name>Сингапурский доллар</Name>
<EngName>Singapore Dollar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01625 </ParentCode>
<ISO_Num_Code>702</ISO_Num_Code>
<ISO_Char_Code>SGD</ISO_Char_Code>
</Item>
<Item ID="R01665A">
<Name>Суринамский доллар</Name>
<EngName>Surinam Dollar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01665 </ParentCode>
<ISO_Num_Code>968</ISO_Num_Code>
<ISO_Char_Code>SRD</ISO_Char_Code>
</Item>
<Item ID="R01670">
<Name>Таджикский сомони</Name>
<EngName>Tajikistan Ruble</EngName>
<Nominal>10</Nominal>
<ParentCode>R01670 </ParentCode>
<ISO_Num_Code>972</ISO_Num_Code>
<ISO_Char_Code>TJS</ISO_Char_Code>
</Item>
<Item ID="R01675">
<Name>Таиландский бат</Name>
<EngName>Thai Baht</EngName>
<Nominal>10</Nominal>
<ParentCode>R01675 </ParentCode>
<ISO_Num_Code>764</ISO_Num_Code>
<ISO_Char_Code>THB</ISO_Char_Code>
</Item>
<Item ID="R01700J">
<Name>Турецкая лира</Name>
<EngName>Turkish Lira</EngName>
<Nominal>10</Nominal>
<ParentCode>R01700 </ParentCode>
<ISO_Num_Code>949</ISO_Num_Code>
<ISO_Char_Code>TRY</ISO_Char_Code>
</Item>
<Item ID="R01710">
<Name>Туркменский манат</Name>
<EngName>Turkmenistan Manat</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01710 </ParentCode>
<ISO_Num_Code>795</ISO_Num_Code>
<ISO_Char_Code>TMM</ISO_Char_Code>
</Item>
<Item ID="R01710A">
<Name>Новый туркменский манат</Name>
<EngName>New Turkmenistan Manat</EngName>
<Nominal>1</Nominal>
<ParentCode>R01710 </ParentCode>
<ISO_Num_Code>934</ISO_Num_Code>
<ISO_Char_Code>TMT</ISO_Char_Code>
</Item>
<Item ID="R01717">
<Name>Узбекский сум</Name>
<EngName>Uzbekistan Sum</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01717 </ParentCode>
<ISO_Num_Code>860</ISO_Num_Code>
<ISO_Char_Code>UZS</ISO_Char_Code>
</Item>
<Item ID="R01720">
<Name>Украинская гривна</Name>
<EngName>Ukrainian Hryvnia</EngName>
<Nominal>10</Nominal>
<ParentCode>R01720 </ParentCode>
<ISO_Num_Code>980</ISO_Num_Code>
<ISO_Char_Code>UAH</ISO_Char_Code>
</Item>
<Item ID="R01720A">
<Name>Украинский карбованец</Name>
<EngName>Ukrainian Hryvnia</EngName>
<Nominal>1</Nominal>
<ParentCode>R01720 </ParentCode>
<ISO_Num_Code/>
<ISO_Char_Code/>
</Item>
<Item ID="R01740">
<Name>Финляндская марка</Name>
<EngName>Finnish Marka</EngName>
<Nominal>100</Nominal>
<ParentCode>R01740 </ParentCode>
<ISO_Num_Code>246</ISO_Num_Code>
<ISO_Char_Code>FIM</ISO_Char_Code>
</Item>
<Item ID="R01750">
<Name>Французский франк</Name>
<EngName>French Franc</EngName>
<Nominal>1000</Nominal>
<ParentCode>R01750 </ParentCode>
<ISO_Num_Code>250</ISO_Num_Code>
<ISO_Char_Code>FRF</ISO_Char_Code>
</Item>
<Item ID="R01760">
<Name>Чешская крона</Name>
<EngName>Czech Koruna</EngName>
<Nominal>10</Nominal>
<ParentCode>R01760 </ParentCode>
<ISO_Num_Code>203</ISO_Num_Code>
<ISO_Char_Code>CZK</ISO_Char_Code>
</Item>
<Item ID="R01770">
<Name>Шведская крона</Name>
<EngName>Swedish Krona</EngName>
<Nominal>10</Nominal>
<ParentCode>R01770 </ParentCode>
<ISO_Num_Code>752</ISO_Num_Code>
<ISO_Char_Code>SEK</ISO_Char_Code>
</Item>
<Item ID="R01775">
<Name>Швейцарский франк</Name>
<EngName>Swiss Franc</EngName>
<Nominal>1</Nominal>
<ParentCode>R01775 </ParentCode>
<ISO_Num_Code>756</ISO_Num_Code>
<ISO_Char_Code>CHF</ISO_Char_Code>
</Item>
<Item ID="R01790">
<Name>ЭКЮ</Name>
<EngName>ECU</EngName>
<Nominal>1</Nominal>
<ParentCode>R01790 </ParentCode>
<ISO_Num_Code>954</ISO_Num_Code>
<ISO_Char_Code>XEU</ISO_Char_Code>
</Item>
<Item ID="R01795">
<Name>Эстонская крона</Name>
<EngName>Estonian Kroon</EngName>
<Nominal>10</Nominal>
<ParentCode>R01795 </ParentCode>
<ISO_Num_Code>233</ISO_Num_Code>
<ISO_Char_Code>EEK</ISO_Char_Code>
</Item>
<Item ID="R01805">
<Name>Югославский новый динар</Name>
<EngName>Yugoslavian Dinar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01804 </ParentCode>
<ISO_Num_Code>890</ISO_Num_Code>
<ISO_Char_Code>YUN</ISO_Char_Code>
</Item>
<Item ID="R01805F">
<Name>Сербский динар</Name>
<EngName>Serbian Dinar</EngName>
<Nominal>100</Nominal>
<ParentCode>R01804 </ParentCode>
<ISO_Num_Code>941</ISO_Num_Code>
<ISO_Char_Code>RSD</ISO_Char_Code>
</Item>
<Item ID="R01810">
<Name>Южноафриканский рэнд</Name>
<EngName>S.African Rand</EngName>
<Nominal>10</Nominal>
<ParentCode>R01810 </ParentCode>
<ISO_Num_Code>710</ISO_Num_Code>
<ISO_Char_Code>ZAR</ISO_Char_Code>
</Item>
<Item ID="R01815">
<Name>Вон Республики Корея</Name>
<EngName>South Korean Won</EngName>
<Nominal>1000</Nominal>
<ParentCode>R01815 </ParentCode>
<ISO_Num_Code>410</ISO_Num_Code>
<ISO_Char_Code>KRW</ISO_Char_Code>
</Item>
<Item ID="R01820">
<Name>Японская иена</Name>
<EngName>Japanese Yen</EngName>
<Nominal>100</Nominal>
<ParentCode>R01820 </ParentCode>
<ISO_Num_Code>392</ISO_Num_Code>
<ISO_Char_Code>JPY</ISO_Char_Code>
</Item>
</Valuta>