diff --git a/client/src/app/shared/atoms/textbox.component.ts b/client/src/app/shared/atoms/textbox.component.ts new file mode 100644 index 0000000..71ab07d --- /dev/null +++ b/client/src/app/shared/atoms/textbox.component.ts @@ -0,0 +1,24 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { AbstractControl, FormControl, FormGroup } from '@angular/forms'; +import { FormComponent } from '../formBuilder/types/form.type'; +import { TextBox } from '../formBuilder/types/atoms-types'; + +@Component({ + selector: 'textbox', + template: ` + + `, +}) +export class TextBoxComponent implements OnInit { + @Input() field!: FormComponent; + @Input() form!: FormControl; + constructor() {} + + ngOnInit() {} +} diff --git a/client/src/app/shared/formBuilder/field-builder/field-builder.component.ts b/client/src/app/shared/formBuilder/field-builder/field-builder.component.ts new file mode 100644 index 0000000..694acf7 --- /dev/null +++ b/client/src/app/shared/formBuilder/field-builder/field-builder.component.ts @@ -0,0 +1,51 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FormComponent } from '../types/form.type'; +import { FormGroup, FormControl } from '@angular/forms'; + +@Component({ + selector: 'field-builder', + template: ` +
+ +
+ +
+ {{ field.body.label }} is required +
+
+
+ `, +}) +export class FieldBuilderComponent implements OnInit { + @Input({ required: true }) field!: FormComponent; + @Input() form!: FormGroup; + + get isValid() { + return this.form.controls[this.field.id].valid; + } + get isDirty() { + return this.form.controls[this.field.id].dirty; + } + + get control(): FormControl { + return this.form.controls[this.field.id] as FormControl; + } + + constructor() {} + + ngOnInit() {} +} diff --git a/client/src/app/shared/formBuilder/form-id.ts b/client/src/app/shared/formBuilder/form-id.ts new file mode 100644 index 0000000..f1b528b --- /dev/null +++ b/client/src/app/shared/formBuilder/form-id.ts @@ -0,0 +1,15 @@ +export class FormID { + private static CHARACTERS = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + private static BASE_ID = 'form-'; + + private static generateRandomString(length: number): string { + let result = ''; + for (let i = 0; i < length; i++) { + result += this.CHARACTERS.charAt( + Math.floor(Math.random() * this.CHARACTERS.length) + ); + } + return result; + } +} diff --git a/client/src/app/shared/formBuilder/formBuilder.component.ts b/client/src/app/shared/formBuilder/formBuilder.component.ts new file mode 100644 index 0000000..1311be0 --- /dev/null +++ b/client/src/app/shared/formBuilder/formBuilder.component.ts @@ -0,0 +1,19 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'form-builder', + template: ` +
+
+ +
+
+
+ `, +}) +export class FormBuilderComponent { + @Input() fields: any[] = []; + @Input() formGroup!: FormGroup; + constructor() {} +} diff --git a/client/src/app/shared/formBuilder/formBuilder.module.ts b/client/src/app/shared/formBuilder/formBuilder.module.ts new file mode 100644 index 0000000..0f63fe6 --- /dev/null +++ b/client/src/app/shared/formBuilder/formBuilder.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { FormBuilderComponent } from './formBuilder.component'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule } from '@angular/forms'; +import { FieldBuilderComponent } from './field-builder/field-builder.component'; +import { TextBoxComponent } from '../atoms/textbox.component'; + +@NgModule({ + imports: [CommonModule, ReactiveFormsModule], + declarations: [FormBuilderComponent, FieldBuilderComponent, TextBoxComponent], + exports: [FormBuilderComponent], + providers: [], +}) +export class FormBuilderModule {} diff --git a/client/src/app/shared/formBuilder/types/atoms-types/index.ts b/client/src/app/shared/formBuilder/types/atoms-types/index.ts new file mode 100644 index 0000000..eb1e3b3 --- /dev/null +++ b/client/src/app/shared/formBuilder/types/atoms-types/index.ts @@ -0,0 +1,34 @@ +type TextBox = { + type: 'text'; + body: { + type: string; + label: string; + name: string; + value: string; + required: boolean; + placeholder: string; + }; +}; + +type CheckBox = { + type: 'checkbox'; + body: { + label: string; + name: string; + value: string; + required: boolean; + placeholder: string; + }; +}; + +type DropDown = { + type: 'dropdown'; + body: { + label: string; + items: string[]; + required: boolean; + placeholder: string; + }; +}; + +export { TextBox, CheckBox, DropDown }; diff --git a/client/src/app/shared/formBuilder/types/form.type.ts b/client/src/app/shared/formBuilder/types/form.type.ts new file mode 100644 index 0000000..7d2b1f0 --- /dev/null +++ b/client/src/app/shared/formBuilder/types/form.type.ts @@ -0,0 +1,6 @@ +import { CheckBox, DropDown, TextBox } from './atoms-types'; + +export type FormComponent = (TextBox | CheckBox | DropDown) & { + id: string; + type: string; +}; diff --git a/recipe_manager.code-workspace b/recipe_manager.code-workspace index e329506..a40211a 100644 --- a/recipe_manager.code-workspace +++ b/recipe_manager.code-workspace @@ -6,5 +6,16 @@ { "path": "./server" } - ] + ], + "settings": { + "sqltools.connections": [ + { + "previewLimit": 50, + "driver": "SQLite", + "name": "Taobin Recipe Manager (Server)", + "database": "${workspaceFolder:server}/data/database.db" + } + ], + "sqltools.useNodeRuntime": true + } } \ No newline at end of file diff --git a/server/cofffeemachineConfig b/server/cofffeemachineConfig index 47004d3..9fc8791 160000 --- a/server/cofffeemachineConfig +++ b/server/cofffeemachineConfig @@ -1 +1 @@ -Subproject commit 47004d38a2c9468fa5cdbadfaf792fb9bd234a1f +Subproject commit 9fc8791818e56cc8bd9065beed223ec96844f146 diff --git a/server/data/database.db b/server/data/database.db new file mode 100644 index 0000000..4005aa1 Binary files /dev/null and b/server/data/database.db differ diff --git a/server/data/migrations/20231127070350_init.down.sql b/server/data/migrations/20231127070350_init.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/server/data/migrations/20231127070350_init.up.sql b/server/data/migrations/20231127070350_init.up.sql new file mode 100644 index 0000000..be67617 --- /dev/null +++ b/server/data/migrations/20231127070350_init.up.sql @@ -0,0 +1,10 @@ +-- slqlite3 +-- create users table +CREATE TABLE users ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT NOT NULL, + email TEXT NOT NULL, + password TEXT NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/server/data/sqlite.go b/server/data/sqlite.go new file mode 100644 index 0000000..e47f694 --- /dev/null +++ b/server/data/sqlite.go @@ -0,0 +1,8 @@ +package data + +import "github.com/jmoiron/sqlx" + +func NewSqliteDatabase() *sqlx.DB { + db := sqlx.MustConnect("sqlite3", "./data/database.db") + return db +} diff --git a/server/go.mod b/server/go.mod index 37aff73..a92e933 100644 --- a/server/go.mod +++ b/server/go.mod @@ -24,7 +24,11 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect ) -require github.com/gorilla/websocket v1.5.0 // indirect +require ( + github.com/gorilla/websocket v1.5.0 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect + github.com/mattn/go-sqlite3 v1.14.18 // indirect +) require ( github.com/fsnotify/fsnotify v1.6.0 // indirect diff --git a/server/go.sum b/server/go.sum index aa64271..106d85d 100644 --- a/server/go.sum +++ b/server/go.sum @@ -71,6 +71,7 @@ github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vz github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -150,6 +151,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -161,8 +164,12 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI= +github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= diff --git a/server/routers/auth.go b/server/routers/auth.go index e9fce2c..f4ca6e8 100644 --- a/server/routers/auth.go +++ b/server/routers/auth.go @@ -10,6 +10,7 @@ import ( "recipe-manager/services/oauth" "github.com/go-chi/chi/v5" + "go.uber.org/zap" "golang.org/x/oauth2" ) @@ -87,6 +88,8 @@ func (ar *AuthRouter) Route(r chi.Router) { value.Add("redirect_to", redirect_to) } + Log.Info("User Log-In Success", zap.String("user", user.Name), zap.String("email", user.Email)) + // redirect to frontend with token and refresh token w.Header().Add("set-cookie", "access_token="+token.AccessToken+"; Path=/; HttpOnly; SameSite=None; Secure; Max-Age=3600") w.Header().Add("set-cookie", "refresh_token="+token.RefreshToken+"; Path=/; HttpOnly; SameSite=None; Secure") diff --git a/server/routers/recipe.go b/server/routers/recipe.go index 9ffca89..c3b94f9 100644 --- a/server/routers/recipe.go +++ b/server/routers/recipe.go @@ -303,7 +303,7 @@ func (rr *RecipeRouter) Route(r chi.Router) { // check if changed // Log.Debug("Check if changed", zap.Any("result", rr.data.GetRecipe01ByProductCode(changes.ProductCode))) - file, _ := os.Create(path.Join("./cofffeemachineConfig", countryID[:3], filename)) + file, _ := os.Create(path.Join("./cofffeemachineConfig", countryID, filename)) if err != nil { Log.Error("Error when tried to create file", zap.Error(err)) return @@ -311,7 +311,7 @@ func (rr *RecipeRouter) Route(r chi.Router) { encoder := json.NewEncoder(file) encoder.SetIndent("", " ") - err = encoder.Encode(rr.data.GetCurrentRecipe()) + err = encoder.Encode(rr.data.GetRecipe(countryID, filename)) if err != nil { Log.Error("Error when write file", zap.Error(err)) diff --git a/server/server.go b/server/server.go index d84e989..5717722 100644 --- a/server/server.go +++ b/server/server.go @@ -154,11 +154,6 @@ func (s *Server) createHandler() { } ctx := context.WithValue(r.Context(), "user", user) - if user == nil { - Log.Error("User is not authenticated or timed out", zap.Any("requester", user)) - } else { - Log.Info("User is authenticated", zap.String("user", user.Name)) - } next.ServeHTTP(w, r.WithContext(ctx)) })