prob still broken, but don\t have time right now
This commit is contained in:
@@ -4,6 +4,9 @@ module ${model.name}
|
|||||||
import freeflowuniverse.herolib.core.base
|
import freeflowuniverse.herolib.core.base
|
||||||
import freeflowuniverse.herolib.core.playbook
|
import freeflowuniverse.herolib.core.playbook
|
||||||
import freeflowuniverse.herolib.ui.console
|
import freeflowuniverse.herolib.ui.console
|
||||||
|
@if model.hasconfig
|
||||||
|
import freeflowuniverse.herolib.data.encoderhero
|
||||||
|
@end
|
||||||
|
|
||||||
@if model.cat == .installer
|
@if model.cat == .installer
|
||||||
import freeflowuniverse.herolib.sysadmin.startupmanager
|
import freeflowuniverse.herolib.sysadmin.startupmanager
|
||||||
@@ -37,24 +40,24 @@ fn args_get (args_ ArgsGet) ArgsGet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(args_ ArgsGet) !&${model.classname} {
|
pub fn get(args_ ArgsGet) !&${model.classname} {
|
||||||
mut model := args_get(args_)
|
mut args := args_get(args_)
|
||||||
if !(model.name in ${model.name}_global) {
|
if !(args.name in ${args.name}_global) {
|
||||||
if model.name=="default"{
|
if args.name=="default"{
|
||||||
if ! config_exists(model){
|
if ! config_exists(args){
|
||||||
if default{
|
if default{
|
||||||
config_save(model)!
|
mut context:=base.context() or { panic("bug") }
|
||||||
|
context.hero_config_set("${model.name}",model.name,heroscript_default()!)!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config_load(model)!
|
load(args)!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ${model.name}_global[model.name] or {
|
return ${args.name}_global[args.name] or {
|
||||||
println(${model.name}_global)
|
println(${args.name}_global)
|
||||||
panic("could not get config for ${model.name} with name:??{model.name}")
|
panic("could not get config for ${args.name} with name:??{model.name}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@else
|
@else
|
||||||
pub fn get(args_ ArgsGet) !&${model.classname} {
|
pub fn get(args_ ArgsGet) !&${model.classname} {
|
||||||
return &${model.classname}{}
|
return &${model.classname}{}
|
||||||
@@ -62,32 +65,38 @@ pub fn get(args_ ArgsGet) !&${model.classname} {
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@if model.hasconfig
|
@if model.hasconfig
|
||||||
fn config_exists(args_ ArgsGet) bool {
|
|
||||||
|
//set the model in mem and the config on the filesystem
|
||||||
|
fn set(o ${model.classname})! {
|
||||||
|
mut o2:=obj_init(o)!
|
||||||
|
${model.name}_global[o.name] = &o2
|
||||||
|
${model.name}_default = o.name
|
||||||
|
}
|
||||||
|
|
||||||
|
//check we find the config on the filesystem
|
||||||
|
fn exists(args_ ArgsGet) bool {
|
||||||
mut model := args_get(args_)
|
mut model := args_get(args_)
|
||||||
mut context:=base.context() or { panic("bug") }
|
mut context:=base.context() or { panic("bug") }
|
||||||
return context.hero_config_exists("${model.name}",model.name)
|
return context.hero_config_exists("${model.name}",model.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_load(args_ ArgsGet) ! {
|
//load the config error if it doesn't exist
|
||||||
|
fn load(args_ ArgsGet) ! {
|
||||||
mut model := args_get(args_)
|
mut model := args_get(args_)
|
||||||
mut context:=base.context()!
|
mut context:=base.context()!
|
||||||
mut heroscript := context.hero_config_get("${model.name}",model.name)!
|
mut heroscript := context.hero_config_get("${model.name}",model.name)!
|
||||||
play(heroscript:heroscript)!
|
play(heroscript:heroscript)!
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_save(args_ ArgsGet) ! {
|
//save the config to the filesystem in the context
|
||||||
mut model := args_get(args_)
|
fn save(o ${model.classname})! {
|
||||||
|
mut model := args_get(args_)
|
||||||
mut context:=base.context()!
|
mut context:=base.context()!
|
||||||
context.hero_config_set("${model.name}",model.name,heroscript_default()!)!
|
heroscript := encoderhero.encode[${model.classname}](o2)!
|
||||||
|
context.hero_config_set("${model.name}",model.name,heroscript)!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn set(o ${model.classname})! {
|
|
||||||
mut o2:=obj_init(o)!
|
|
||||||
${model.name}_global[o.name] = &o2
|
|
||||||
${model.name}_default = o.name
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
^^[params]
|
^^[params]
|
||||||
pub struct PlayArgs {
|
pub struct PlayArgs {
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
module base
|
|
||||||
|
|
||||||
import json
|
|
||||||
// import freeflowuniverse.herolib.ui.console
|
|
||||||
|
|
||||||
// is an object which has a configurator, session and config object which is unique for the model
|
|
||||||
// T is the Config Object
|
|
||||||
|
|
||||||
pub struct BaseConfig[T] {
|
|
||||||
mut:
|
|
||||||
configurator_ ?Configurator[T] @[skip; str: skip]
|
|
||||||
config_ ?&T
|
|
||||||
session_ ?&Session @[skip; str: skip]
|
|
||||||
configtype string
|
|
||||||
pub mut:
|
|
||||||
instance string
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut self BaseConfig[T]) session() !&Session {
|
|
||||||
mut mysession := self.session_ or {
|
|
||||||
mut c := context()!
|
|
||||||
mut r := c.redis()!
|
|
||||||
incrkey := 'sessions:base:latest:${self.configtype}:${self.instance}'
|
|
||||||
latestid := r.incr(incrkey)!
|
|
||||||
name := '${self.configtype}_${self.instance}_${latestid}'
|
|
||||||
mut s := c.session_new(name: name)!
|
|
||||||
self.session_ = &s
|
|
||||||
&s
|
|
||||||
}
|
|
||||||
|
|
||||||
return mysession
|
|
||||||
}
|
|
||||||
|
|
||||||
// management class of the configs of this obj
|
|
||||||
pub fn (mut self BaseConfig[T]) configurator() !&Configurator[T] {
|
|
||||||
if self.configurator_ == none {
|
|
||||||
mut c := configurator_new[T](
|
|
||||||
instance: self.instance
|
|
||||||
)!
|
|
||||||
self.configurator_ = c
|
|
||||||
}
|
|
||||||
return &(self.configurator_ or { return error('configurator not initialized') })
|
|
||||||
}
|
|
||||||
|
|
||||||
// will overwrite the config
|
|
||||||
pub fn (mut self BaseConfig[T]) config_set(myconfig T) ! {
|
|
||||||
self.config_ = &myconfig
|
|
||||||
self.config_save()!
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut self BaseConfig[T]) config_new() !&T {
|
|
||||||
config := self.config_ or {
|
|
||||||
mut configurator := self.configurator()!
|
|
||||||
mut c := configurator.new()!
|
|
||||||
self.config_ = &c
|
|
||||||
&c
|
|
||||||
}
|
|
||||||
|
|
||||||
self.config_save()!
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut self BaseConfig[T]) config() !&T {
|
|
||||||
mut config := self.config_ or { return error('config was not initialized yet') }
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut self BaseConfig[T]) config_get() !&T {
|
|
||||||
mut mycontext := context()!
|
|
||||||
mut config := self.config_ or {
|
|
||||||
mut configurator := self.configurator()!
|
|
||||||
if !(configurator.exists()!) {
|
|
||||||
mut mycfg := self.config_new()!
|
|
||||||
return mycfg
|
|
||||||
}
|
|
||||||
|
|
||||||
mut db := mycontext.db_config_get()!
|
|
||||||
if !db.exists(key: configurator.config_key())! {
|
|
||||||
return error("can't find configuration with name: ${configurator.config_key()} in context:'${mycontext.config.name}'")
|
|
||||||
}
|
|
||||||
data := db.get(key: configurator.config_key())!
|
|
||||||
|
|
||||||
mut c := json.decode(T, data)!
|
|
||||||
$for field in T.fields {
|
|
||||||
field_attrs := attrs_get(field.attrs)
|
|
||||||
if 'secret' in field_attrs {
|
|
||||||
// QUESTION: is it ok if we only support encryption for string fields
|
|
||||||
$if field.typ is string {
|
|
||||||
v := c.$(field.name)
|
|
||||||
c.$(field.name) = mycontext.secret_decrypt(v)!
|
|
||||||
// console.print_debug('FIELD DECRYPTED: ${field} ${field.name}')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.config_ = &c
|
|
||||||
&c
|
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut self BaseConfig[T]) config_save() ! {
|
|
||||||
mut config2 := *self.config()! // dereference so we don't modify the original
|
|
||||||
mut mycontext := context()!
|
|
||||||
// //walk over the properties see where they need to be encrypted, if yes encrypt
|
|
||||||
$for field in T.fields {
|
|
||||||
field_attrs := attrs_get(field.attrs)
|
|
||||||
if 'secret' in field_attrs {
|
|
||||||
// QUESTION: is it ok if we only support encryption for string fields
|
|
||||||
$if field.typ is string {
|
|
||||||
v := config2.$(field.name)
|
|
||||||
config2.$(field.name) = mycontext.secret_encrypt(v)!
|
|
||||||
}
|
|
||||||
// console.print_debug('FIELD ENCRYPTED: ${field.name}')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mut configurator := self.configurator()!
|
|
||||||
configurator.set(config2)!
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut self BaseConfig[T]) config_delete() ! {
|
|
||||||
mut configurator := self.configurator()!
|
|
||||||
configurator.delete()!
|
|
||||||
self.config_ = none
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Action {
|
|
||||||
set
|
|
||||||
get
|
|
||||||
new
|
|
||||||
delete
|
|
||||||
}
|
|
||||||
|
|
||||||
// init our class with the base session_args
|
|
||||||
pub fn (mut self BaseConfig[T]) init(configtype string, instance string, action Action, myconfig T) ! {
|
|
||||||
self.instance = instance
|
|
||||||
self.configtype = configtype
|
|
||||||
if action == .get {
|
|
||||||
self.config_get()!
|
|
||||||
} else if action == .new {
|
|
||||||
self.config_new()!
|
|
||||||
} else if action == .delete {
|
|
||||||
self.config_delete()!
|
|
||||||
} else if action == .set {
|
|
||||||
self.config_set(myconfig)!
|
|
||||||
} else {
|
|
||||||
panic('bug')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// will return {'name': 'teststruct', 'params': ''}
|
|
||||||
fn attrs_get(attrs []string) map[string]string {
|
|
||||||
mut out := map[string]string{}
|
|
||||||
for i in attrs {
|
|
||||||
if i.contains('=') {
|
|
||||||
kv := i.split('=')
|
|
||||||
out[kv[0].trim_space().to_lower()] = kv[1].trim_space().to_lower()
|
|
||||||
} else {
|
|
||||||
out[i.trim_space().to_lower()] = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
module base
|
|
||||||
|
|
||||||
import json
|
|
||||||
import freeflowuniverse.herolib.ui.console
|
|
||||||
|
|
||||||
@[heap]
|
|
||||||
pub struct Configurator[T] {
|
|
||||||
pub mut:
|
|
||||||
// context &Context @[skip; str: skip]
|
|
||||||
instance string
|
|
||||||
description string
|
|
||||||
configured bool
|
|
||||||
configtype string // e.g. sshclient
|
|
||||||
}
|
|
||||||
|
|
||||||
@[params]
|
|
||||||
pub struct ConfiguratorArgs {
|
|
||||||
pub mut:
|
|
||||||
// context &Context // optional context for the configurator
|
|
||||||
instance string @[required]
|
|
||||||
}
|
|
||||||
|
|
||||||
// name is e.g. mailclient (the type of configuration setting)
|
|
||||||
// instance is the instance of the config e.g. kds
|
|
||||||
// the context defines the context in which we operate, is optional will get the default one if not set
|
|
||||||
pub fn configurator_new[T](args ConfiguratorArgs) !Configurator[T] {
|
|
||||||
return Configurator[T]{
|
|
||||||
// context: args.context
|
|
||||||
configtype: T.name.to_lower()
|
|
||||||
instance: args.instance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut self Configurator[T]) config_key() string {
|
|
||||||
return '${self.configtype}_config_${self.instance}'
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the full configuration as one object to dbconfig
|
|
||||||
pub fn (mut self Configurator[T]) set(args T) ! {
|
|
||||||
mut mycontext := context()!
|
|
||||||
mut db := mycontext.db_config_get()!
|
|
||||||
data := json.encode_pretty(args)
|
|
||||||
db.set(key: self.config_key(), value: data)!
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut self Configurator[T]) exists() !bool {
|
|
||||||
mut mycontext := context()!
|
|
||||||
mut db := mycontext.db_config_get()!
|
|
||||||
return db.exists(key: self.config_key())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut self Configurator[T]) new() !T {
|
|
||||||
return T{
|
|
||||||
instance: self.instance
|
|
||||||
description: self.description
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut self Configurator[T]) get() !T {
|
|
||||||
mut mycontext := context()!
|
|
||||||
mut db := mycontext.db_config_get()!
|
|
||||||
if !db.exists(key: self.config_key())! {
|
|
||||||
return error("can't find configuration with name: ${self.config_key()} in context:'${mycontext.config.name}'")
|
|
||||||
}
|
|
||||||
data := db.get(key: self.config_key())!
|
|
||||||
return json.decode(T, data)!
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut self Configurator[T]) delete() ! {
|
|
||||||
mut mycontext := context()!
|
|
||||||
mut db := mycontext.db_config_get()!
|
|
||||||
db.delete(key: self.config_key())!
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut self Configurator[T]) getset(args T) !T {
|
|
||||||
mut mycontext := context()!
|
|
||||||
mut db := mycontext.db_config_get()!
|
|
||||||
if db.exists(key: self.config_key())! {
|
|
||||||
return self.get()!
|
|
||||||
}
|
|
||||||
self.set(args)!
|
|
||||||
return self.get()!
|
|
||||||
}
|
|
||||||
|
|
||||||
@[params]
|
|
||||||
pub struct PrintArgs {
|
|
||||||
pub mut:
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut self Configurator[T]) list() ![]string {
|
|
||||||
panic('implement')
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut self Configurator[T]) configprint(args PrintArgs) ! {
|
|
||||||
mut mycontext := context()!
|
|
||||||
mut db := mycontext.db_config_get()!
|
|
||||||
if args.name.len > 0 {
|
|
||||||
if db.exists(key: self.config_key())! {
|
|
||||||
data := db.get(key: self.config_key())!
|
|
||||||
c := json.decode(T, data)!
|
|
||||||
console.print_debug('${c}')
|
|
||||||
console.print_debug('')
|
|
||||||
} else {
|
|
||||||
return error("Can't find connection with name: ${args.name}")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic('implement')
|
|
||||||
// for item in list()! {
|
|
||||||
// // console.print_debug(" ==== $item")
|
|
||||||
// configprint(name: item)!
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// init our class with the base session_args
|
|
||||||
// pub fn (mut self Configurator[T]) init(session_args_ SessionNewArgs) ! {
|
|
||||||
// self.session_=session_args.session or {
|
|
||||||
// session_new(session_args)!
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -3,7 +3,6 @@ module base
|
|||||||
import freeflowuniverse.herolib.data.paramsparser
|
import freeflowuniverse.herolib.data.paramsparser
|
||||||
import freeflowuniverse.herolib.core.redisclient
|
import freeflowuniverse.herolib.core.redisclient
|
||||||
import freeflowuniverse.herolib.data.dbfs
|
import freeflowuniverse.herolib.data.dbfs
|
||||||
// import freeflowuniverse.herolib.crypt.secp256k1
|
|
||||||
import freeflowuniverse.herolib.crypt.aes_symmetric
|
import freeflowuniverse.herolib.crypt.aes_symmetric
|
||||||
import freeflowuniverse.herolib.ui
|
import freeflowuniverse.herolib.ui
|
||||||
import freeflowuniverse.herolib.ui.console
|
import freeflowuniverse.herolib.ui.console
|
||||||
@@ -134,18 +133,18 @@ pub fn (mut self Context) db_config_get() !dbfs.DB {
|
|||||||
pub fn (mut self Context) hero_config_set(cat string, name string, content_ string) ! {
|
pub fn (mut self Context) hero_config_set(cat string, name string, content_ string) ! {
|
||||||
mut content := texttools.dedent(content_)
|
mut content := texttools.dedent(content_)
|
||||||
content = rootpath.shell_expansion(content)
|
content = rootpath.shell_expansion(content)
|
||||||
path := '${os.home_dir()}/hero/context/${self.config.name}/${cat}__${name}.yaml'
|
path := '${self.path()!.path}/${cat}__${name}.yaml'
|
||||||
mut config_file := pathlib.get_file(path: path)!
|
mut config_file := pathlib.get_file(path: path)!
|
||||||
config_file.write(content)!
|
config_file.write(content)!
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut self Context) hero_config_exists(cat string, name string) bool {
|
pub fn (mut self Context) hero_config_exists(cat string, name string) !bool {
|
||||||
path := '${os.home_dir()}/hero/context/${self.config.name}/${cat}__${name}.yaml'
|
path := '${self.path()!.path}/${cat}__${name}.yaml'
|
||||||
return os.exists(path)
|
return os.exists(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut self Context) hero_config_get(cat string, name string) !string {
|
pub fn (mut self Context) hero_config_get(cat string, name string) !string {
|
||||||
path := '${os.home_dir()}/hero/context/${self.config.name}/${cat}__${name}.yaml'
|
path := '${self.path()!.path}/${cat}__${name}.yaml'
|
||||||
mut config_file := pathlib.get_file(path: path, create: false)!
|
mut config_file := pathlib.get_file(path: path, create: false)!
|
||||||
return config_file.read()!
|
return config_file.read()!
|
||||||
}
|
}
|
||||||
@@ -187,12 +186,10 @@ pub fn (mut self Context) secret_set(secret_ string) ! {
|
|||||||
self.save()!
|
self.save()!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub fn (mut self Context) path() !pathlib.Path {
|
pub fn (mut self Context) path() !pathlib.Path {
|
||||||
return self.path_ or {
|
return self.path_ or {
|
||||||
path := '${os.home_dir()}/hero/context/${self.config.name}'
|
path2 := '${os.home_dir()}/hero/context/${self.config.name}'
|
||||||
mut path := pathlib.get_dir(path: path,create: false)!
|
mut path := pathlib.get_dir(path: path2,create: false)!
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import freeflowuniverse.herolib.data.paramsparser
|
|||||||
import freeflowuniverse.herolib.data.dbfs
|
import freeflowuniverse.herolib.data.dbfs
|
||||||
import freeflowuniverse.herolib.core.logger
|
import freeflowuniverse.herolib.core.logger
|
||||||
import json
|
import json
|
||||||
// import freeflowuniverse.herolib.core.pathlib
|
import freeflowuniverse.herolib.core.pathlib
|
||||||
// import freeflowuniverse.herolib.develop.gittools
|
// import freeflowuniverse.herolib.develop.gittools
|
||||||
// import freeflowuniverse.herolib.ui.console
|
// import freeflowuniverse.herolib.ui.console
|
||||||
|
|
||||||
@[heap]
|
@[heap]
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
mut:
|
mut:
|
||||||
path_ ?pathlib.Path
|
path_ ?pathlib.Path
|
||||||
logger_ ?logger.Logger
|
logger_ ?logger.Logger
|
||||||
pub mut:
|
pub mut:
|
||||||
name string // unique id for session (session id), can be more than one per context
|
name string // unique id for session (session id), can be more than one per context
|
||||||
@@ -95,8 +95,8 @@ pub fn (self Session) guid() string {
|
|||||||
|
|
||||||
pub fn (mut self Session) path() !pathlib.Path {
|
pub fn (mut self Session) path() !pathlib.Path {
|
||||||
return self.path_ or {
|
return self.path_ or {
|
||||||
path := '${self.context.path().path}/${self.name}'
|
path2 := '${self.context.path()!.path}/${self.name}'
|
||||||
mut path := pathlib.get_dir(path: path,create: true)!
|
mut path := pathlib.get_dir(path: path2,create: true)!
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module base
|
|||||||
|
|
||||||
import freeflowuniverse.herolib.core.logger
|
import freeflowuniverse.herolib.core.logger
|
||||||
|
|
||||||
pub fn (session Session) logger() !logger.Logger {
|
pub fn (mut session Session) logger() !logger.Logger {
|
||||||
return session.logger_ or {
|
return session.logger_ or {
|
||||||
mut l2 := logger.new("${session.path()!.path}/logs")!
|
mut l2 := logger.new("${session.path()!.path}/logs")!
|
||||||
l2
|
l2
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ const person = Person{
|
|||||||
id: 1
|
id: 1
|
||||||
name: 'Bob'
|
name: 'Bob'
|
||||||
age: 21
|
age: 21
|
||||||
birthday: time.new_time(
|
birthday: time.new(
|
||||||
day: 12
|
day: 12
|
||||||
month: 12
|
month: 12
|
||||||
year: 2012
|
year: 2012
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const person = Person{
|
|||||||
id: 1
|
id: 1
|
||||||
name: 'Bob'
|
name: 'Bob'
|
||||||
age: 21
|
age: 21
|
||||||
birthday: time.new_time(
|
birthday: time.new(
|
||||||
day: 12
|
day: 12
|
||||||
month: 12
|
month: 12
|
||||||
year: 2012
|
year: 2012
|
||||||
|
|||||||
Reference in New Issue
Block a user