5 changed files with 230 additions and 0 deletions
@ -1,4 +1,12 @@ |
|||||||
|
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= |
||||||
|
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= |
||||||
|
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= |
||||||
|
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= |
||||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= |
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= |
||||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= |
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= |
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= |
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= |
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= |
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= |
||||||
|
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU= |
||||||
|
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s= |
||||||
|
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= |
||||||
|
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= |
||||||
|
|||||||
@ -0,0 +1,94 @@ |
|||||||
|
package db |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"database/sql" |
||||||
|
"errors" |
||||||
|
"time" |
||||||
|
|
||||||
|
_ "github.com/tursodatabase/libsql-client-go/libsql" |
||||||
|
) |
||||||
|
|
||||||
|
type Progress struct { |
||||||
|
ID string |
||||||
|
UserID string |
||||||
|
LevelID string |
||||||
|
Status string |
||||||
|
CompletedAt sql.NullTime |
||||||
|
AttemptData sql.NullString |
||||||
|
Stars sql.NullInt64 |
||||||
|
CreatedAt time.Time |
||||||
|
UpdatedAt time.Time |
||||||
|
} |
||||||
|
|
||||||
|
type ProgressClient struct { |
||||||
|
db *sql.DB |
||||||
|
} |
||||||
|
|
||||||
|
func NewProgressClient(db *sql.DB) *ProgressClient { |
||||||
|
return &ProgressClient{db: db} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *ProgressClient) UpsertProgress(ctx context.Context, p *Progress) error { |
||||||
|
_, err := c.db.ExecContext(ctx, ` |
||||||
|
INSERT INTO progress (id, user_id, level_id, status, completed_at, attempt_data, stars) |
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?) |
||||||
|
ON CONFLICT(user_id, level_id) DO UPDATE SET |
||||||
|
status = excluded.status, |
||||||
|
completed_at = excluded.completed_at, |
||||||
|
attempt_data = excluded.attempt_data, |
||||||
|
stars = excluded.stars, |
||||||
|
updated_at = CURRENT_TIMESTAMP |
||||||
|
`, p.ID, p.UserID, p.LevelID, p.Status, p.CompletedAt, p.AttemptData, p.Stars) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func (c *ProgressClient) GetProgressByUser(ctx context.Context, userID string) ([]*Progress, error) { |
||||||
|
rows, err := c.db.QueryContext(ctx, ` |
||||||
|
SELECT id, user_id, level_id, status, completed_at, attempt_data, stars, created_at, updated_at |
||||||
|
FROM progress |
||||||
|
WHERE user_id = ? |
||||||
|
`, userID) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
defer rows.Close() |
||||||
|
|
||||||
|
var progressList []*Progress |
||||||
|
for rows.Next() { |
||||||
|
var p Progress |
||||||
|
err := rows.Scan(&p.ID, &p.UserID, &p.LevelID, &p.Status, &p.CompletedAt, &p.AttemptData, &p.Stars, &p.CreatedAt, &p.UpdatedAt) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
progressList = append(progressList, &p) |
||||||
|
} |
||||||
|
|
||||||
|
return progressList, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *ProgressClient) GetProgress(ctx context.Context, userID, levelID string) (*Progress, error) { |
||||||
|
row := c.db.QueryRowContext(ctx, ` |
||||||
|
SELECT id, user_id, level_id, status, completed_at, attempt_data, stars, created_at, updated_at |
||||||
|
FROM progress |
||||||
|
WHERE user_id = ? AND level_id = ? |
||||||
|
`, userID, levelID) |
||||||
|
|
||||||
|
var p Progress |
||||||
|
err := row.Scan(&p.ID, &p.UserID, &p.LevelID, &p.Status, &p.CompletedAt, &p.AttemptData, &p.Stars, &p.CreatedAt, &p.UpdatedAt) |
||||||
|
if err != nil { |
||||||
|
if errors.Is(err, sql.ErrNoRows) { |
||||||
|
return nil, nil |
||||||
|
} |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return &p, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *ProgressClient) DeleteProgress(ctx context.Context, userID, levelID string) error { |
||||||
|
_, err := c.db.ExecContext(ctx, ` |
||||||
|
DELETE FROM progress |
||||||
|
WHERE user_id = ? AND level_id = ? |
||||||
|
`, userID, levelID) |
||||||
|
return err |
||||||
|
} |
||||||
@ -0,0 +1,64 @@ |
|||||||
|
package db |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"database/sql" |
||||||
|
"time" |
||||||
|
|
||||||
|
_ "github.com/tursodatabase/libsql-client-go/libsql" |
||||||
|
) |
||||||
|
|
||||||
|
type Subscription struct { |
||||||
|
ID string |
||||||
|
UserID string |
||||||
|
StripeID string |
||||||
|
Status string |
||||||
|
StartedAt time.Time |
||||||
|
EndsAt sql.NullTime |
||||||
|
} |
||||||
|
|
||||||
|
type SubscriptionClient struct { |
||||||
|
db *sql.DB |
||||||
|
} |
||||||
|
|
||||||
|
func NewSubscriptionClient(db *sql.DB) *SubscriptionClient { |
||||||
|
return &SubscriptionClient{db: db} |
||||||
|
} |
||||||
|
|
||||||
|
// Insert or update subscription
|
||||||
|
func (c *SubscriptionClient) Upsert(ctx context.Context, s *Subscription) error { |
||||||
|
_, err := c.db.ExecContext(ctx, ` |
||||||
|
INSERT INTO subscriptions (id, user_id, stripe_id, status, started_at, ends_at) |
||||||
|
VALUES (?, ?, ?, ?, ?, ?) |
||||||
|
ON CONFLICT(id) DO UPDATE SET |
||||||
|
status = excluded.status, |
||||||
|
started_at = excluded.started_at, |
||||||
|
ends_at = excluded.ends_at |
||||||
|
`, s.ID, s.UserID, s.StripeID, s.Status, s.StartedAt, s.EndsAt) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// Get by user ID
|
||||||
|
func (c *SubscriptionClient) GetByUserID(ctx context.Context, userID string) (*Subscription, error) { |
||||||
|
row := c.db.QueryRowContext(ctx, ` |
||||||
|
SELECT id, user_id, stripe_id, status, started_at, ends_at |
||||||
|
FROM subscriptions |
||||||
|
WHERE user_id = ? |
||||||
|
`, userID) |
||||||
|
|
||||||
|
var s Subscription |
||||||
|
err := row.Scan(&s.ID, &s.UserID, &s.StripeID, &s.Status, &s.StartedAt, &s.EndsAt) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return &s, nil |
||||||
|
} |
||||||
|
|
||||||
|
// Delete by user ID
|
||||||
|
func (c *SubscriptionClient) DeleteByUserID(ctx context.Context, userID string) error { |
||||||
|
_, err := c.db.ExecContext(ctx, ` |
||||||
|
DELETE FROM subscriptions |
||||||
|
WHERE user_id = ? |
||||||
|
`, userID) |
||||||
|
return err |
||||||
|
} |
||||||
@ -0,0 +1,57 @@ |
|||||||
|
package db |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"database/sql" |
||||||
|
"time" |
||||||
|
|
||||||
|
_ "github.com/tursodatabase/libsql-client-go/libsql" |
||||||
|
) |
||||||
|
|
||||||
|
type User struct { |
||||||
|
ID string |
||||||
|
GitHubID int64 |
||||||
|
GitHubLogin string |
||||||
|
AvatarURL string |
||||||
|
Email string |
||||||
|
CreatedAt time.Time |
||||||
|
UpdatedAt time.Time |
||||||
|
} |
||||||
|
|
||||||
|
type UserClient struct { |
||||||
|
db *sql.DB |
||||||
|
} |
||||||
|
|
||||||
|
func NewUserClient(db *sql.DB) *UserClient { |
||||||
|
return &UserClient{db: db} |
||||||
|
} |
||||||
|
|
||||||
|
func (uc *UserClient) UpsertUser(ctx context.Context, user User) error { |
||||||
|
_, err := uc.db.ExecContext(ctx, ` |
||||||
|
INSERT INTO users (id, github_id, github_login, avatar_url, email) |
||||||
|
VALUES (?, ?, ?, ?, ?) |
||||||
|
ON CONFLICT(github_id) DO UPDATE SET |
||||||
|
github_login = excluded.github_login, |
||||||
|
avatar_url = excluded.avatar_url, |
||||||
|
email = excluded.email, |
||||||
|
updated_at = CURRENT_TIMESTAMP |
||||||
|
`, user.ID, user.GitHubID, user.GitHubLogin, user.AvatarURL, user.Email) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func (uc *UserClient) GetUserByGitHubID(ctx context.Context, githubID int64) (*User, error) { |
||||||
|
row := uc.db.QueryRowContext(ctx, ` |
||||||
|
SELECT id, github_id, github_login, avatar_url, email, created_at, updated_at |
||||||
|
FROM users WHERE github_id = ? |
||||||
|
`, githubID) |
||||||
|
|
||||||
|
var user User |
||||||
|
err := row.Scan( |
||||||
|
&user.ID, &user.GitHubID, &user.GitHubLogin, |
||||||
|
&user.AvatarURL, &user.Email, &user.CreatedAt, &user.UpdatedAt, |
||||||
|
) |
||||||
|
if err == sql.ErrNoRows { |
||||||
|
return nil, nil |
||||||
|
} |
||||||
|
return &user, err |
||||||
|
} |
||||||
Loading…
Reference in new issue