add discord bot!

This commit is contained in:
eccentric 2023-12-13 22:52:16 +00:00
parent 2f6a9d52e4
commit f35e2ea77c
13 changed files with 453 additions and 28 deletions

View File

@ -4,6 +4,7 @@ import (
"math/rand" "math/rand"
"os" "os"
"os/signal" "os/signal"
"strconv"
"syscall" "syscall"
"github.com/goccy/go-json" "github.com/goccy/go-json"
@ -35,3 +36,23 @@ func RandomString(n int) string {
} }
return string(s) return string(s)
} }
func FormatNumber(number int) string {
str := ""
for i, char := range ReverseString(strconv.Itoa(number)) {
if i % 3 == 0 && i != 0 {
str += ","
}
str += string(char)
}
return ReverseString(str)
}
func ReverseString(input string) string {
str := ""
for _, char := range input {
str = string(char) + str
}
return str
}

View File

@ -17,6 +17,7 @@ type CS struct {
ID string ID string
Secret string Secret string
Token string Token string
Guild string
} }
Output struct { Output struct {
Level string Level string
@ -82,6 +83,11 @@ func LoadConfig(file []byte) {
panic("Discord Bot Token is empty") panic("Discord Bot Token is empty")
} }
Config.Discord.Guild = cfg.Section("discord").Key("guild").String()
if Config.Discord.Guild == "" {
panic("Discord Guild ID is empty")
}
Config.API.Host = cfg.Section("api").Key("host").String() Config.API.Host = cfg.Section("api").Key("host").String()
if Config.API.Host == "" { if Config.API.Host == "" {
panic("API Host is empty") panic("API Host is empty")

View File

@ -13,6 +13,8 @@ id="1234567890..."
secret="abcdefg..." secret="abcdefg..."
; discord bot token ; discord bot token
token="OTK...." token="OTK...."
; server id
guild="1234567890..."
[output] [output]
; level of logging ; level of logging

90
discord/discord.go Normal file
View File

@ -0,0 +1,90 @@
package discord
import (
"strings"
"github.com/bwmarrin/discordgo"
"github.com/ectrc/snow/aid"
)
type DiscordCommand struct {
Command *discordgo.ApplicationCommand
Handler func(s *discordgo.Session, i *discordgo.InteractionCreate)
AdminOnly bool
}
type DiscordModal struct {
ID string
Handler func(s *discordgo.Session, i *discordgo.InteractionCreate)
}
type DiscordClient struct {
Client *discordgo.Session
Commands map[string]*DiscordCommand
Modals map[string]*DiscordModal
}
var StaticClient *DiscordClient
func NewDiscordClient(token string) *DiscordClient {
client, err := discordgo.New("Bot " + token)
if err != nil {
panic(err)
}
client.Identify.Intents = discordgo.IntentsAllWithoutPrivileged
return &DiscordClient{
Client: client,
Commands: make(map[string]*DiscordCommand),
Modals: make(map[string]*DiscordModal),
}
}
func IntialiseClient() {
StaticClient = NewDiscordClient(aid.Config.Discord.Token)
StaticClient.Client.AddHandler(StaticClient.readyHandler)
StaticClient.Client.AddHandler(StaticClient.interactionHandler)
addCommands()
for _, command := range StaticClient.Commands {
StaticClient.RegisterCommand(command)
}
err := StaticClient.Client.Open()
if err != nil {
panic(err)
}
}
func (c *DiscordClient) RegisterCommand(command *DiscordCommand) {
if command.AdminOnly {
adminDefaultPermission := int64(discordgo.PermissionAdministrator)
command.Command.DefaultMemberPermissions = &adminDefaultPermission
}
_, err := c.Client.ApplicationCommandCreate(aid.Config.Discord.ID, aid.Config.Discord.Guild, command.Command)
if err != nil {
aid.Print("Failed to register command: " + command.Command.Name)
return
}
}
func (c *DiscordClient) readyHandler(s *discordgo.Session, event *discordgo.Ready) {
aid.Print("Discord bot is ready")
}
func (c *DiscordClient) interactionHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
switch i.Type {
case discordgo.InteractionApplicationCommand:
if command, ok := c.Commands[i.ApplicationCommandData().Name]; ok {
command.Handler(s, i)
}
case discordgo.InteractionModalSubmit:
if modal, ok := c.Modals[strings.Split(i.ModalSubmitData().CustomID, "://")[0]]; ok {
modal.Handler(s, i)
}
}
}

62
discord/embed.go Normal file
View File

@ -0,0 +1,62 @@
package discord
import "github.com/bwmarrin/discordgo"
type EmbedBuilder struct {
Embed discordgo.MessageEmbed
}
func NewEmbedBuilder() *EmbedBuilder {
return &EmbedBuilder{
Embed: discordgo.MessageEmbed{},
}
}
func (e *EmbedBuilder) SetTitle(title string) *EmbedBuilder {
e.Embed.Title = title
return e
}
func (e *EmbedBuilder) SetDescription(description string) *EmbedBuilder {
e.Embed.Description = description
return e
}
func (e *EmbedBuilder) SetColor(color int) *EmbedBuilder {
e.Embed.Color = color
return e
}
func (e *EmbedBuilder) SetImage(url string) *EmbedBuilder {
e.Embed.Image = &discordgo.MessageEmbedImage{
URL: url,
}
return e
}
func (e *EmbedBuilder) SetThumbnail(url string) *EmbedBuilder {
e.Embed.Thumbnail = &discordgo.MessageEmbedThumbnail{
URL: url,
}
return e
}
func (e *EmbedBuilder) SetFooter(text string) *EmbedBuilder {
e.Embed.Footer = &discordgo.MessageEmbedFooter{
Text: text,
}
return e
}
func (e *EmbedBuilder) AddField(name, value string, inline bool) *EmbedBuilder {
e.Embed.Fields = append(e.Embed.Fields, &discordgo.MessageEmbedField{
Name: name,
Value: value,
Inline: inline,
})
return e
}
func (e *EmbedBuilder) Build() *discordgo.MessageEmbed {
return &e.Embed
}

196
discord/handlers.go Normal file
View File

@ -0,0 +1,196 @@
package discord
import (
"github.com/bwmarrin/discordgo"
"github.com/ectrc/snow/aid"
"github.com/ectrc/snow/fortnite"
"github.com/ectrc/snow/person"
"github.com/ectrc/snow/storage"
)
var (
)
func addCommand(command *DiscordCommand) {
StaticClient.Commands[command.Command.Name] = command
}
func addModal(modal *DiscordModal) {
StaticClient.Modals[modal.ID] = modal
}
func addCommands() {
if StaticClient == nil {
panic("StaticClient is nil")
}
addCommand(&DiscordCommand{
Command: &discordgo.ApplicationCommand{
Name: "create",
Description: "Create an account with the bot.",
},
Handler: createHandler,
})
addModal(&DiscordModal{
ID: "create",
Handler: createModalHandler,
})
addCommand(&DiscordCommand{
Command: &discordgo.ApplicationCommand{
Name: "information",
Description: "Useful information about this server's activity! Admin Only.",
},
Handler: informationHandler,
AdminOnly: true,
})
addCommand(&DiscordCommand{
Command: &discordgo.ApplicationCommand{
Name: "delete",
Description: "Delete your account with the bot.",
},
Handler: deleteHandler,
})
}
func createHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
modal := &discordgo.InteractionResponseData{
CustomID: "create://" + i.Member.User.ID,
Title: "Create an account",
Components: []discordgo.MessageComponent{
&discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
discordgo.TextInput{
CustomID: "display",
Label: "DISPLAY NAME",
Style: discordgo.TextInputShort,
Placeholder: "Enter your crazy display name here!",
Required: true,
MaxLength: 20,
MinLength: 2,
},
},
},
},
}
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseModal,
Data: modal,
})
}
func createModalHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
data := i.ModalSubmitData()
if len(data.Components) <= 0 {
aid.Print("No components found")
return
}
components, ok := data.Components[0].(*discordgo.ActionsRow)
if !ok {
aid.Print("Failed to assert TextInput")
return
}
display, ok := components.Components[0].(*discordgo.TextInput)
if !ok {
aid.Print("Failed to assert TextInput")
return
}
found := person.FindByDiscord(i.Member.User.ID)
if found != nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
NewEmbedBuilder().SetTitle("Account already exists").SetDescription("You already have an account with the display name: `"+ found.DisplayName +"`").SetColor(0xda373c).Build(),
},
},
})
return
}
found = person.FindByDisplay(display.Value)
if found != nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
NewEmbedBuilder().SetTitle("Account already exists").SetDescription("An account with that display name already exists, please try a different name.").SetColor(0xda373c).Build(),
},
},
})
return
}
account := fortnite.NewFortnitePerson(display.Value, aid.RandomString(10), false) // or aid.Config.Fortnite.Everything
discord := &storage.DB_DiscordPerson{
ID: i.Member.User.ID,
PersonID: account.ID,
Username: i.Member.User.Username,
}
storage.Repo.SaveDiscordPerson(discord)
account.Discord = discord
account.Save()
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
NewEmbedBuilder().SetTitle("Account created").SetDescription("Your account has been created with the display name: `"+ account.DisplayName +"`").SetColor(0x2093dc).Build(),
},
},
})
}
func deleteHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
found := person.FindByDiscord(i.Member.User.ID)
if found == nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
NewEmbedBuilder().SetTitle("Account not found").SetDescription("You don't have an account with the bot.").SetColor(0xda373c).Build(),
},
},
})
return
}
storage.Repo.DeleteDiscordPerson(found.Discord.ID)
found.Delete()
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
NewEmbedBuilder().SetTitle("Account deleted").SetDescription("Your account has been deleted.").SetColor(0x2093dc).Build(),
},
},
})
}
func informationHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
playerCount := storage.Repo.GetPersonsCount()
totalVbucks := storage.Repo.TotalVBucks()
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
NewEmbedBuilder().
SetTitle("Information").
SetDescription("Useful information about this server's activity!").
SetColor(0x2093dc).
AddField("Players Registered", aid.FormatNumber(playerCount), true).
AddField("Players Online", aid.FormatNumber(0), true).
AddField("VBucks in Circulation", aid.FormatNumber(totalVbucks), false).
Build(),
},
},
})
}

View File

@ -22,7 +22,7 @@ var (
} }
) )
func NewFortnitePerson(displayName string, key string) *p.Person { func NewFortnitePerson(displayName string, key string, everything bool) *p.Person {
person := p.NewPerson() person := p.NewPerson()
person.DisplayName = displayName person.DisplayName = displayName
person.AccessKey = key person.AccessKey = key
@ -115,7 +115,7 @@ func NewFortnitePerson(displayName string, key string) *p.Person {
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("last_applied_loadout", loadout.ID)).Save() person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("last_applied_loadout", loadout.ID)).Save()
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("active_loadout_index", 0)).Save() person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("active_loadout_index", 0)).Save()
if aid.Config.Fortnite.Everything { if everything {
for _, item := range Cosmetics.Items { for _, item := range Cosmetics.Items {
if strings.Contains(strings.ToLower(item.ID), "random") { if strings.Contains(strings.ToLower(item.ID), "random") {
continue continue
@ -131,3 +131,15 @@ func NewFortnitePerson(displayName string, key string) *p.Person {
return person return person
} }
func GiveEverything(person *p.Person) {
for _, item := range Cosmetics.Items {
if strings.Contains(strings.ToLower(item.ID), "random") {
continue
}
item := p.NewItem(item.Type.BackendValue + ":" + item.ID, 1)
item.HasSeen = true
person.AthenaProfile.Items.AddItem(item).Save()
}
}

2
go.mod
View File

@ -16,7 +16,9 @@ require (
require ( require (
github.com/andybalholm/brotli v1.0.6 // indirect github.com/andybalholm/brotli v1.0.6 // indirect
github.com/bwmarrin/discordgo v0.27.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect

4
go.sum
View File

@ -48,6 +48,8 @@ github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@ -157,6 +159,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=

18
main.go
View File

@ -5,10 +5,10 @@ import (
"fmt" "fmt"
"github.com/ectrc/snow/aid" "github.com/ectrc/snow/aid"
"github.com/ectrc/snow/discord"
"github.com/ectrc/snow/fortnite" "github.com/ectrc/snow/fortnite"
"github.com/ectrc/snow/handlers" "github.com/ectrc/snow/handlers"
"github.com/ectrc/snow/storage" "github.com/ectrc/snow/storage"
"github.com/google/uuid"
"github.com/goccy/go-json" "github.com/goccy/go-json"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
@ -38,23 +38,9 @@ func init() {
} }
func init() { func init() {
discord.IntialiseClient()
fortnite.PreloadCosmetics(aid.Config.Fortnite.Season) fortnite.PreloadCosmetics(aid.Config.Fortnite.Season)
fortnite.GenerateRandomStorefront() fortnite.GenerateRandomStorefront()
if aid.Config.Database.DropAllTables {
person := fortnite.NewFortnitePerson("ac", "1")
discord := &storage.DB_DiscordPerson{
ID: uuid.New().String(),
PersonID: person.ID,
}
storage.Repo.SaveDiscordPerson(discord)
// person.DiscordID = discord.ID
person.Discord = discord
person.Save()
}
fortnite.GeneratePlaylistImages() fortnite.GeneratePlaylistImages()
} }

View File

@ -291,3 +291,7 @@ func (p *Person) Snapshot() *PersonSnapshot {
DiscordID: p.Discord.ID, DiscordID: p.Discord.ID,
} }
} }
func (p *Person) Delete() {
storage.Repo.DeletePerson(p.ID)
}

View File

@ -128,12 +128,35 @@ func (s *PostgresStorage) GetAllPersons() []*DB_Person {
return dbPersons return dbPersons
} }
func (s *PostgresStorage) GetPersonsCount() int {
var count int64
s.Postgres.Model(&DB_Person{}).Count(&count)
return int(count)
}
func (s *PostgresStorage) TotalVBucks() int {
var total int64
s.Postgres.Model(&DB_Item{}).Select("sum(quantity)").Where("template_id = ?", "Currency:MtxPurchased").Find(&total)
return int(total)
}
func (s *PostgresStorage) SavePerson(person *DB_Person) { func (s *PostgresStorage) SavePerson(person *DB_Person) {
s.Postgres.Save(person) s.Postgres.Save(person)
} }
func (s *PostgresStorage) DeletePerson(personId string) { func (s *PostgresStorage) DeletePerson(personId string) {
s.Postgres.Delete(&DB_Person{}, "id = ?", personId) s.Postgres.
Model(&DB_Person{}).
Preload("Profiles").
Preload("Profiles.Loadouts").
Preload("Profiles.Items.Variants").
Preload("Profiles.Gifts.Loot").
Preload("Profiles.Attributes").
Preload("Profiles.Items").
Preload("Profiles.Gifts").
Preload("Profiles.Quests").
Preload("Discord").
Delete(&DB_Person{}, "id = ?", personId)
} }
func (s *PostgresStorage) SaveProfile(profile *DB_Profile) { func (s *PostgresStorage) SaveProfile(profile *DB_Profile) {

View File

@ -7,11 +7,16 @@ var (
type Storage interface { type Storage interface {
Migrate(table interface{}, tableName string) Migrate(table interface{}, tableName string)
GetAllPersons() []*DB_Person
GetPersonsCount() int
GetPerson(personId string) *DB_Person GetPerson(personId string) *DB_Person
GetPersonByDisplay(displayName string) *DB_Person GetPersonByDisplay(displayName string) *DB_Person
GetPersonByDiscordID(discordId string) *DB_Person GetPersonByDiscordID(discordId string) *DB_Person
GetAllPersons() []*DB_Person
SavePerson(person *DB_Person) SavePerson(person *DB_Person)
DeletePerson(personId string)
TotalVBucks() int
SaveProfile(profile *DB_Profile) SaveProfile(profile *DB_Profile)
DeleteProfile(profileId string) DeleteProfile(profileId string)
@ -85,10 +90,22 @@ func (r *Repository) GetAllPersons() []*DB_Person {
return r.Storage.GetAllPersons() return r.Storage.GetAllPersons()
} }
func (r *Repository) GetPersonsCount() int {
return r.Storage.GetPersonsCount()
}
func (r *Repository) TotalVBucks() int {
return r.Storage.TotalVBucks()
}
func (r *Repository) SavePerson(person *DB_Person) { func (r *Repository) SavePerson(person *DB_Person) {
r.Storage.SavePerson(person) r.Storage.SavePerson(person)
} }
func (r *Repository) DeletePerson(personId string) {
r.Storage.DeletePerson(personId)
}
func (r *Repository) SaveProfile(profile *DB_Profile) { func (r *Repository) SaveProfile(profile *DB_Profile) {
r.Storage.SaveProfile(profile) r.Storage.SaveProfile(profile)
} }