require (
github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0
github.com/gin-gonic/gin v1.10.0
+ github.com/mattn/go-sqlite3 v1.14.24
+ golang.org/x/oauth2 v0.24.0
+ gopkg.in/yaml.v3 v3.0.1
)
require (
+ cloud.google.com/go/compute/metadata v0.3.0 // indirect
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
- gopkg.in/yaml.v3 v3.0.1 // indirect
)
+cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
+cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04=
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
+golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
CREATE TABLE admin (
admin_id INTEGER PRIMARY KEY,
id TEXT,
+ session_id TEXT,
pw TEXT
);
CREATE TABLE story (
id TEXT,
title TEXT,
date_marked TEXT
- primary_media_id TEXT,
+ primary_media_name TEXT,
content TEXT
);
-CREATE TABLE media (
- media_id INTEGER PRIMARY KEY,
- id TEXT,
- kind TEXT,
- content BLOB
-)
\ No newline at end of file
+
func test_db() error {
- err := pkgdb.OpenDB(pkgglob.G_CONF.Db.Addr, pkgglob.G_CONF.Db.InitFile)
+ err := pkgdb.OpenDB(pkgglob.G_CONF.Db.Addr)
+
+ if err != nil {
+ return err
+ }
+
+ err = pkgdb.Init(pkgglob.G_CONF.Db.InitFile)
if err != nil {
return err
import (
"fmt"
- "os"
+
+ pkgdb "our-wedding-rsvp/pkg/db"
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
return state
}
-func RegisterAdmins(admins map[string]string) error {
-
- err := os.RemoveAll("./data/admin")
-
- if err != nil {
- return fmt.Errorf("failed to remove data/admin")
- }
+func RegisterAdmin(id string, pw string) error {
- err = os.MkdirAll("./data/admin", 0755)
+ err := pkgdb.UpsertAdmin(id, pw)
if err != nil {
-
- return fmt.Errorf("failed to create data/admin")
- }
-
- for k, v := range admins {
-
- ADMINS[k] = v
-
- name := "./data/admin/" + k + ".json"
-
- err := os.WriteFile(name, []byte("{}"), 0644)
-
- if err != nil {
-
- return fmt.Errorf("failed to create data/admin: %s: %s", k, err.Error())
- }
-
+ return fmt.Errorf("failed to register admin")
}
return nil
}
-
-/*
-
-func OauthGoogleLogin(c *gin.Context) {
-
- my_key, my_type, _ := WhoAmI(c)
-
- if my_key != "" && my_type != "" {
-
- fmt.Printf("oauth login: already logged in\n")
-
- c.JSON(http.StatusBadRequest, com.SERVER_RE{Status: "error", Reply: "already logged in"})
-
- return
-
- }
-
- if !USE_OAUTH2 {
-
- c.Redirect(302, "/signin/idiot")
-
- return
- }
-
- oauth_state := GenerateStateAuthCookie(c)
-
- u := GoogleOauthConfig.AuthCodeURL(oauth_state)
-
- c.Redirect(302, u)
-
-}
-
-func OauthGoogleCallback(c *gin.Context) {
-
- my_key, my_type, _ := WhoAmI(c)
-
- if my_key != "" && my_type != "" {
-
- fmt.Printf("oauth callback: already logged in\n")
-
- c.JSON(http.StatusBadRequest, com.SERVER_RE{Status: "error", Reply: "already logged in"})
-
- return
-
- }
-
- session := sessions.Default(c)
-
- var session_id string
-
- v := session.Get("SOLIAGAIN")
-
- if v == nil {
- fmt.Printf("access auth failed: %s\n", "session id not found")
- return
- } else {
- session_id = v.(string)
- }
-
- state := c.Request.FormValue("state")
-
- if state == "" {
- fmt.Printf("access auth failed: %s\n", "form state not found")
- return
- }
-
- if state != session_id {
- fmt.Printf("access auth failed: %s\n", "value not matching")
- c.Redirect(302, "/signin")
- return
- }
-
- data, err := GetUserDataFromGoogle(c.Request.FormValue("code"))
- if err != nil {
- fmt.Printf("access auth failed: %s\n", err.Error())
- c.Redirect(302, "/signin")
- return
- }
-
- var oauth_struct OAuthStruct
-
- err = json.Unmarshal(data, &oauth_struct)
-
- if err != nil {
- fmt.Printf("access auth failed: %s\n", err.Error())
- c.Redirect(302, "/signin")
- return
- }
-
- if !oauth_struct.VERIFIED_EMAIL {
- fmt.Printf("access auth failed: %s\n", err.Error())
- c.Redirect(302, "/signin")
- return
- }
-
- if err != nil {
- fmt.Printf("access auth failed: %s\n", err.Error())
- c.Redirect(302, "/signin")
- return
- }
-
- as, err := dbquery.GetByIdFromAdmin(oauth_struct.EMAIL)
-
- if as == nil {
-
- fmt.Printf("access auth failed: %s", err.Error())
-
- c.Redirect(302, "/signin")
-
- return
-
- }
-
- err = dbquery.MakeSessionForAdmin(session_id, oauth_struct.EMAIL)
-
- if err != nil {
-
- fmt.Printf("make session failed for admin: %s", err.Error())
-
- c.Redirect(302, "/signin")
-
- return
-
- }
-
- fmt.Println("oauth sign in success")
-
- c.Redirect(302, "/")
-
-}
-
-*/
--- /dev/null
+package auth
--- /dev/null
+package auth
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/google"
+)
+
+type OAuthJSON struct {
+ Web struct {
+ ClientID string `json:"client_id"`
+ ProjectID string `json:"project_id"`
+ AuthURI string `json:"auth_uri"`
+ TokenURI string `json:"token_uri"`
+ AuthProviderX509CertURL string `json:"auth_provider_x509_cert_url"`
+ ClientSecret string `json:"client_secret"`
+ RedirectUris []string `json:"redirect_uris"`
+ } `json:"web"`
+}
+
+const OauthGoogleUrlAPI = "https://www.googleapis.com/oauth2/v2/userinfo?access_token="
+
+type OAuthStruct struct {
+ ID string `json:"id"`
+ EMAIL string `json:"email"`
+ VERIFIED_EMAIL bool `json:"verified_email"`
+ PICTURE string `json:"picture"`
+}
+
+var OAUTH_JSON OAuthJSON
+
+var GoogleOauthConfig *oauth2.Config
+
+func InitAuth() {
+
+ if !USE_OAUTH2 {
+ return
+ }
+ OAUTH_JSON = GetOAuthJSON()
+
+ GoogleOauthConfig = GenerateGoogleOauthConfig()
+
+}
+
+func GetOAuthJSON() OAuthJSON {
+
+ var cj OAuthJSON
+
+ file_byte, err := os.ReadFile("oauth.json")
+
+ if err != nil {
+ panic(err)
+ }
+
+ err = json.Unmarshal(file_byte, &cj)
+
+ if err != nil {
+ panic(err)
+ }
+
+ return cj
+
+}
+
+func GenerateGoogleOauthConfig() *oauth2.Config {
+
+ google_oauth_config := &oauth2.Config{
+ ClientID: OAUTH_JSON.Web.ClientID,
+ ClientSecret: OAUTH_JSON.Web.ClientSecret,
+ Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"},
+ Endpoint: google.Endpoint,
+ }
+
+ if DEBUG {
+
+ google_oauth_config.RedirectURL = OAUTH_JSON.Web.RedirectUris[0]
+
+ } else {
+
+ google_oauth_config.RedirectURL = OAUTH_JSON.Web.RedirectUris[1]
+ }
+
+ fmt.Println(google_oauth_config.RedirectURL)
+
+ return google_oauth_config
+
+}
+
+func GetUserDataFromGoogle(code string) ([]byte, error) {
+
+ token, err := GoogleOauthConfig.Exchange(context.Background(), code)
+ if err != nil {
+ return nil, fmt.Errorf("code exchange wrong: %s", err.Error())
+ }
+ response, err := http.Get(OauthGoogleUrlAPI + token.AccessToken)
+ if err != nil {
+ return nil, fmt.Errorf("failed getting user info: %s", err.Error())
+ }
+ defer response.Body.Close()
+ contents, err := io.ReadAll(response.Body)
+ if err != nil {
+ return nil, fmt.Errorf("failed read response: %s", err.Error())
+ }
+ return contents, nil
+}
--- /dev/null
+package auth
+
+import (
+ "log"
+ pkgdb "our-wedding-rsvp/pkg/db"
+
+ "github.com/gin-gonic/contrib/sessions"
+ "github.com/gin-gonic/gin"
+)
+
+func Is0(c *gin.Context) bool {
+
+ session := sessions.Default(c)
+
+ var session_id string
+
+ v := session.Get("RSVP")
+
+ if v == nil {
+ return false
+
+ } else {
+
+ session_id = v.(string)
+
+ if session_id == "" {
+ return false
+ }
+
+ }
+
+ s, err := pkgdb.GetAdminBySessionId(session_id)
+
+ if err != nil {
+ log.Printf("is0: err: %v\n", err)
+ return false
+ }
+
+ if s == nil {
+ return false
+ }
+
+ return true
+}
--- /dev/null
+package auth
+
+import (
+ "strings"
+ "unicode"
+)
+
+func VerifyMediaKey(mediakey string) bool {
+
+ mklist := strings.SplitN(mediakey, ".", 2)
+
+ for _, c := range mklist[0] {
+
+ if unicode.IsLetter(c) {
+
+ continue
+
+ } else if unicode.IsDigit(c) {
+
+ continue
+
+ } else {
+
+ return false
+ }
+
+ }
+
+ for _, c := range mklist[1] {
+
+ if unicode.IsLetter(c) {
+
+ continue
+
+ } else if unicode.IsDigit(c) {
+
+ continue
+
+ } else {
+
+ return false
+ }
+
+ }
+
+ return true
+}
import (
"database/sql"
"fmt"
+ "log"
+ "os"
_ "github.com/mattn/go-sqlite3"
)
var _db *sql.DB
-func OpenDB(addr string, initfile string) error {
+var initiated bool = false
- db, err := sql.Open("sqlite3", addr)
+type SqliteMaster struct {
+ TblName string
+}
+
+func query(query string, args []any) (*sql.Rows, error) {
+
+ var empty_row *sql.Rows
+
+ results, err := _db.Query(query, args[0:]...)
if err != nil {
- return err
+ return empty_row, fmt.Errorf("db query: %s", err.Error())
+
}
- _db = db
+ return results, err
- err = runInitSql(initfile)
+}
+
+func exec(query string, args []any) error {
+
+ result, err := _db.Exec(query, args[0:]...)
if err != nil {
+ return fmt.Errorf("db exec: %s", err.Error())
+ }
- _db.Close()
+ _, err = result.RowsAffected()
+
+ if err != nil {
- return fmt.Errorf("failed to run init sql: %v", err)
+ return fmt.Errorf("db exec: rows: %s", err.Error())
}
return nil
+
}
-func Query(query string, args []any) (*sql.Rows, error) {
+func OpenDB(addr string) error {
- var empty_row *sql.Rows
+ db, err := sql.Open("sqlite3", addr)
- results, err := _db.Query(query, args[0:]...)
+ if err != nil {
+
+ return err
+ }
+
+ _db = db
+
+ return nil
+}
+
+func Init(initfile string) error {
+
+ tables := make([]SqliteMaster, 0)
+
+ q := `
+
+ SELECT
+ tbl_name
+ FROM
+ sqlite_master
+ WHERE
+ type='table'
+ `
+
+ a := []any{}
+
+ res, err := query(q, a)
if err != nil {
- return empty_row, fmt.Errorf("db query: %s", err.Error())
+ return fmt.Errorf("failed to get tables: %v", err)
+ }
+
+ defer res.Close()
+
+ for res.Next() {
+
+ t := SqliteMaster{}
+
+ err = res.Scan(&t.TblName)
+
+ if err != nil {
+
+ return fmt.Errorf("failed to read table record: %v", err)
+
+ }
+
+ tables = append(tables, t)
}
- return results, err
+ tlen := len(tables)
+
+ if tlen == 0 {
+ log.Printf("no tables found\n")
+
+ err = createFromInitSql(initfile)
+
+ if err != nil {
+
+ return fmt.Errorf("create from init sql: %v", err)
+ }
+
+ log.Printf("tables successfully created\n")
+
+ }
+
+ initiated = true
+
+ return nil
}
-func Exec(query string, args []any) error {
+func createFromInitSql(initfile string) error {
- result, err := _db.Exec(query, args[0:]...)
+ file_b, err := os.ReadFile(initfile)
if err != nil {
- return fmt.Errorf("db exec: %s", err.Error())
+
+ return fmt.Errorf("failed to read initfile: %v", err)
}
- _, err = result.RowsAffected()
+ q := string(file_b)
+
+ a := []any{}
+
+ err = exec(q, a)
if err != nil {
+ return fmt.Errorf("failed to init: %v", err)
+ }
- return fmt.Errorf("db exec: rows: %s", err.Error())
+ tables := make([]SqliteMaster, 0)
+
+ q = `
+
+ SELECT
+ tbl_name
+ FROM
+ sqlite_master
+ WHERE
+ type='table'
+ `
+
+ a = []any{}
+
+ res, err := query(q, a)
+
+ if err != nil {
+
+ return fmt.Errorf("failed to get tables: %v", err)
}
- return nil
+ defer res.Close()
-}
+ for res.Next() {
-func runInitSql(initfile string) error {
+ t := SqliteMaster{}
- return nil
+ err = res.Scan(&t.TblName)
+ if err != nil {
+
+ return fmt.Errorf("failed to read table record: %v", err)
+
+ }
+
+ tables = append(tables, t)
+
+ }
+
+ tlen := len(tables)
+
+ if tlen == 0 {
+
+ return fmt.Errorf("failed to assert tables")
+ }
+
+ return nil
}
--- /dev/null
+package db
+
+import (
+ "database/sql"
+ "fmt"
+)
+
+type Admin struct {
+ AdminId int
+ Id string
+ SessionId sql.NullString
+ Pw sql.NullString
+}
+
+func GetAdminById(id string) (*Admin, error) {
+
+ admins := []Admin{}
+
+ q := `
+
+ SELECT
+ admin_id,
+ id,
+ pw
+ FROM
+ admin
+ WHERE
+ id = ?
+
+ `
+
+ a := []any{
+ id,
+ }
+
+ res, err := query(q, a)
+
+ if err != nil {
+
+ return nil, fmt.Errorf("failed to get admin: %v", err)
+ }
+
+ defer res.Close()
+
+ for res.Next() {
+
+ admin := Admin{}
+
+ err = res.Scan(
+ &admin.AdminId,
+ &admin.Id,
+ &admin.Pw,
+ )
+
+ if err != nil {
+
+ return nil, fmt.Errorf("failed to get admin record: %v", err)
+ }
+
+ admins = append(admins, admin)
+
+ }
+
+ rlen := len(admins)
+
+ if rlen > 1 {
+ return nil, fmt.Errorf("admin record: invalid record len: %d", rlen)
+ }
+
+ if rlen == 0 {
+ return nil, nil
+ }
+
+ return &admins[0], nil
+
+}
+
+func UpsertAdmin(id string, pw string) error {
+
+ admin, err := GetAdminById(id)
+
+ if err != nil {
+ return fmt.Errorf("failed to get admin: %v", err)
+ }
+
+ q := ""
+
+ a := []any{}
+
+ if admin == nil {
+
+ q = `
+
+ INSERT INTO admin (
+ id,
+ pw
+ )
+ VALUES (
+ ?,
+ ?
+ )
+
+ `
+
+ a = append(a, id)
+ a = append(a, pw)
+
+ } else {
+
+ q = `
+
+ UPDATE
+ admin
+ SET
+ id = ?,
+ pw = ?
+ WHERE
+ admin_id = ?
+
+ `
+ a = append(a, id)
+ a = append(a, pw)
+ a = append(a, admin.AdminId)
+
+ }
+
+ err = exec(q, a)
+
+ if err != nil {
+ return fmt.Errorf("failed to upsert admin: %v", err)
+ }
+
+ return nil
+}
+
+func GetAdminBySessionId(session_id string) (*Admin, error) {
+
+ admins := []Admin{}
+
+ q := `
+
+ SELECT
+ admin_id,
+ id,
+ session_id,
+ pw
+ FROM
+ admin
+ WHERE
+ session_id = ?
+ AND session_id IS NOT NULL
+
+ `
+
+ a := []any{
+ session_id,
+ }
+
+ res, err := query(q, a)
+
+ if err != nil {
+
+ return nil, fmt.Errorf("failed to get admin: %v", err)
+ }
+
+ defer res.Close()
+
+ for res.Next() {
+
+ admin := Admin{}
+
+ err = res.Scan(
+ &admin.AdminId,
+ &admin.Id,
+ &admin.SessionId,
+ &admin.Pw,
+ )
+
+ if err != nil {
+
+ return nil, fmt.Errorf("failed to get admin record: %v", err)
+ }
+
+ admins = append(admins, admin)
+
+ }
+
+ rlen := len(admins)
+
+ if rlen != 1 {
+ return nil, nil
+ }
+
+ return &admins[0], nil
+
+}
+
+func SetAdminSessionId(id string, session_id string, signin bool) error {
+
+ admin, err := GetAdminById(id)
+
+ if err != nil {
+ return fmt.Errorf("no such admin: %s", id)
+ }
+
+ q := ""
+ a := []any{}
+
+ if signin {
+ q = `
+
+ UPDATE
+ admin
+ SET
+ session_id = ?
+ WHERE
+ admin_id = ?
+
+ `
+
+ a = append(a, session_id)
+ a = append(a, admin.AdminId)
+
+ } else {
+
+ q = `
+
+ UPDATE
+ admin
+ SET
+ session_id = NULL
+ WHERE
+ admin_id = ?
+
+ `
+
+ a = append(a, admin.AdminId)
+
+ }
+
+ err = exec(q, a)
+
+ if err != nil {
+
+ return fmt.Errorf("failed to set session: %v", err)
+ }
+
+ return nil
+}
--- /dev/null
+package db
+
+import (
+ "encoding/json"
+ "fmt"
+ "mime/multipart"
+ "os"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+)
+
+var mediaPath = "./data/media/"
+
+func UploadMedia(c *gin.Context, file *multipart.FileHeader, new_filename string) error {
+
+ this_file_path := mediaPath + new_filename
+
+ err := c.SaveUploadedFile(file, this_file_path)
+
+ if err != nil {
+
+ return fmt.Errorf("failed to upload: %s", err.Error())
+ }
+
+ return nil
+}
+
+func DownloadMedia(c *gin.Context, watchId string) error {
+
+ this_media_path := mediaPath + watchId
+
+ if _, err := os.Stat(this_media_path); err != nil {
+
+ return err
+
+ }
+
+ c.File(this_media_path)
+
+ return nil
+}
+
+func DeleteMedia(media_key string) error {
+
+ this_media_path := mediaPath + media_key
+
+ err := os.Remove(this_media_path)
+
+ if err != nil {
+
+ return fmt.Errorf("failed to delete media: rm: %s", err.Error())
+ }
+
+ return nil
+}
+
+func GetAssociateMediaKeysForEditorjsSrc(rawArticle []byte) ([]string, error) {
+
+ var retlist []string
+
+ var editorjsSrc map[string]interface{}
+
+ err := json.Unmarshal(rawArticle, &editorjsSrc)
+
+ if err != nil {
+
+ return nil, fmt.Errorf("failed to unmarshal: %s", err.Error())
+
+ }
+
+ blocks, okay := editorjsSrc["blocks"]
+
+ if !okay {
+
+ return nil, fmt.Errorf("invalid format: %s", "no blocks")
+ }
+
+ blocksList := blocks.([]interface{})
+
+ blocksLen := len(blocksList)
+
+ for i := 0; i < blocksLen; i++ {
+
+ blockObj := blocksList[i].(map[string]interface{})
+
+ objType, okay := blockObj["type"]
+
+ if !okay {
+ continue
+ }
+
+ if objType != "image" {
+ continue
+ }
+
+ objData, okay := blockObj["data"]
+
+ if !okay {
+ continue
+ }
+
+ objFields := objData.(map[string]interface{})
+
+ fileField, okay := objFields["file"]
+
+ if !okay {
+ continue
+ }
+
+ targetProps := fileField.(map[string]interface{})
+
+ urlTarget, okay := targetProps["url"]
+
+ if !okay {
+ continue
+ }
+
+ target := urlTarget.(string)
+
+ pathList := strings.Split(target, "/")
+
+ keyExt := pathList[len(pathList)-1]
+
+ keyExtList := strings.Split(keyExt, ".")
+
+ key := keyExtList[0]
+
+ retlist = append(retlist, key)
+ }
+
+ return retlist, nil
+}
--- /dev/null
+package db
+
+import "fmt"
+
+type Story struct {
+ StoryId int
+ Id string
+ Title string
+ DateMarked string
+ PrimaryMediaName string
+ Content string
+}
+
+func SaveStory(id string, title string, dateMarked string, primaryMediaName string, content string) error {
+
+ q := `
+
+ INSERT INTO story (
+
+ id,
+ title,
+ date_marked,
+ primary_media_name,
+ content
+ )
+ VALUES (
+ ?,
+ ?,
+ ?,
+ ?,
+ ?
+ )
+
+ `
+
+ a := []any{
+ id,
+ title,
+ dateMarked,
+ primaryMediaName,
+ content,
+ }
+
+ err := exec(q, a)
+
+ if err != nil {
+ return fmt.Errorf("failed to save story: %v", err)
+ }
+
+ return nil
+}
+
+func GetStoryById(id string) (*Story, error) {
+
+ stories := []Story{}
+
+ q := `
+
+ SELECT
+ *
+ FROM
+ story
+ WHERE
+ id = ?
+ `
+
+ a := []any{
+ id,
+ }
+
+ res, err := query(q, a)
+
+ if err != nil {
+
+ return nil, fmt.Errorf("failed to get story by id: %v", err)
+ }
+
+ defer res.Close()
+
+ for res.Next() {
+
+ story := Story{}
+
+ err = res.Scan(
+ &story.StoryId,
+ &story.Id,
+ &story.Title,
+ &story.DateMarked,
+ &story.PrimaryMediaName,
+ &story.Content,
+ )
+
+ if err != nil {
+ return nil, fmt.Errorf("failed to get story record: %v", err)
+ }
+
+ stories = append(stories, story)
+
+ }
+
+ rlen := len(stories)
+
+ if rlen != 1 {
+
+ return nil, fmt.Errorf("invalid story record len: %d", rlen)
+ }
+
+ return &stories[0], nil
+
+}
+
+func GetStoryByTitle(title string) (*Story, error) {
+
+ stories := []Story{}
+
+ q := `
+
+ SELECT
+ *
+ FROM
+ story
+ WHERE
+ title = ?
+ `
+
+ a := []any{
+ title,
+ }
+
+ res, err := query(q, a)
+
+ if err != nil {
+
+ return nil, fmt.Errorf("failed to get story by title: %v", err)
+ }
+
+ defer res.Close()
+
+ for res.Next() {
+
+ story := Story{}
+
+ err = res.Scan(
+ &story.StoryId,
+ &story.Id,
+ &story.Title,
+ &story.DateMarked,
+ &story.PrimaryMediaName,
+ &story.Content,
+ )
+
+ if err != nil {
+ return nil, fmt.Errorf("failed to get story record: %v", err)
+ }
+
+ stories = append(stories, story)
+
+ }
+
+ rlen := len(stories)
+
+ if rlen != 1 {
+
+ return nil, fmt.Errorf("invalid story record len: %d", rlen)
+ }
+
+ return &stories[0], nil
+
+}
+
+func DeleteStoryByTitle(title string) (*Story, error) {
+
+ story, err := GetStoryByTitle(title)
+
+ if err != nil {
+
+ return nil, fmt.Errorf("failed to get story by title")
+ }
+
+ q := `
+
+ DELETE
+ FROM
+ story
+ WHERE
+ story_id = ?
+
+ `
+ a := []any{
+ story.StoryId,
+ }
+
+ err = exec(q, a)
+
+ if err != nil {
+ return nil, fmt.Errorf("failed to delete story record")
+ }
+
+ return story, nil
+
+}
package api
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+
+ pkgauth "our-wedding-rsvp/pkg/auth"
+ pkgdb "our-wedding-rsvp/pkg/db"
+
+ "github.com/gin-gonic/contrib/sessions"
+ "github.com/gin-gonic/gin"
+)
+
+type CLIENT_REQ struct {
+ Data string `json:"data"`
+}
+
+type SERVER_RESP struct {
+ Status string `json:"status"`
+ Reply string `json:"reply"`
+}
+
+func OauthGoogleLogin(c *gin.Context) {
+
+ if pkgauth.Is0(c) {
+
+ log.Printf("oauth login: already logged in\n")
+
+ c.JSON(http.StatusBadRequest, SERVER_RESP{Status: "error", Reply: "already logged in"})
+
+ return
+
+ }
+
+ oauth_state := pkgauth.GenerateStateAuthCookie(c)
+
+ u := pkgauth.GoogleOauthConfig.AuthCodeURL(oauth_state)
+
+ c.Redirect(302, u)
+
+}
+
+func OauthGoogleCallback(c *gin.Context) {
+
+ if pkgauth.Is0(c) {
+
+ fmt.Printf("oauth callback: already logged in\n")
+
+ c.JSON(http.StatusBadRequest, SERVER_RESP{Status: "error", Reply: "already logged in"})
+
+ return
+
+ }
+
+ session := sessions.Default(c)
+
+ var session_id string
+
+ v := session.Get("RSVP")
+
+ if v == nil {
+ log.Printf("access auth failed: %s\n", "session id not found")
+ return
+ } else {
+ session_id = v.(string)
+ }
+
+ state := c.Request.FormValue("state")
+
+ if state == "" {
+ log.Printf("access auth failed: %s\n", "form state not found")
+ return
+ }
+
+ if state != session_id {
+ log.Printf("access auth failed: %s\n", "value not matching")
+ c.Redirect(302, "/")
+ return
+ }
+
+ data, err := pkgauth.GetUserDataFromGoogle(c.Request.FormValue("code"))
+ if err != nil {
+ log.Printf("access auth failed: %s\n", err.Error())
+ c.Redirect(302, "/")
+ return
+ }
+
+ var oauth_struct pkgauth.OAuthStruct
+
+ err = json.Unmarshal(data, &oauth_struct)
+
+ if err != nil {
+ log.Printf("access auth failed: %s\n", err.Error())
+ c.Redirect(302, "/")
+ return
+ }
+
+ if !oauth_struct.VERIFIED_EMAIL {
+ log.Printf("access auth failed: %s\n", err.Error())
+ c.Redirect(302, "/")
+ return
+ }
+
+ if err != nil {
+ log.Printf("access auth failed: %s\n", err.Error())
+ c.Redirect(302, "/")
+ return
+ }
+
+ as, err := pkgdb.GetAdminById(oauth_struct.EMAIL)
+
+ if as == nil {
+
+ log.Printf("access auth failed: %s", err.Error())
+
+ c.Redirect(302, "/")
+
+ return
+
+ }
+
+ err = pkgdb.SetAdminSessionId(oauth_struct.EMAIL, session_id, true)
+
+ if err != nil {
+
+ log.Printf("make session failed for admin: %s", err.Error())
+
+ c.Redirect(302, "/")
+
+ return
+
+ }
+
+ log.Println("oauth sign in success")
+
+ c.Redirect(302, "/")
+
+}
import (
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
+
+ pkgserverapi "our-wedding-rsvp/pkg/server/api"
)
func CreateServerFromConfig() (*gin.Engine, error) {
e.GET("/signin", getSignin)
+ e.GET("/api/oauth2/google/signin", pkgserverapi.OauthGoogleLogin)
+
+ e.GET("/oauth2/google/callback", pkgserverapi.OauthGoogleCallback)
+
e.GET("/story/r/:storyId", getRead)
e.GET("/story/w", getWrite)
import (
"github.com/gin-gonic/gin"
+
+ pkgauth "our-wedding-rsvp/pkg/auth"
)
func getIndex(c *gin.Context) {
func getSignin(c *gin.Context) {
+ if pkgauth.USE_OAUTH2 {
+
+ c.Redirect(302, "/api/oauth2/google/signin")
+
+ return
+
+ }
+
c.HTML(200, "signin.html", gin.H{})
}
func getWrite(c *gin.Context) {
+ if !pkgauth.Is0(c) {
+ c.HTML(200, "index.html", gin.H{})
+ return
+ }
+
c.HTML(200, "write.html", gin.H{})
}
#!/bin/bash
-mkdir -p data
-
-if [ ! -f "data/rsvp.db" ]
-then
-
- sqlite3 data/rsvp.db "VACUUM;"
-
-fi
+./start_mkdb.sh
./rsvp.out
--- /dev/null
+#!/bin/bash
+
+
+mkdir -p data
+
+mkdir -p data/media
+
+
+if [ ! -f "data/rsvp.db" ]
+then
+
+ sqlite3 data/rsvp.db "VACUUM;"
+
+fi
+
+
+