moved a lot
This commit is contained in:
1 parent
5772a7d855
commit
136a1c34b3
98 files changed
+6950
-2183
No files matched your search
@@ -0,0 +1,14 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
func VoteMessage(issueID, optionID int64, nonce []byte) [32]byte {
|
||||
buf := make([]byte, 16+len(nonce))
|
||||
binary.BigEndian.PutUint64(buf[0:8], uint64(issueID))
|
||||
binary.BigEndian.PutUint64(buf[8:16], uint64(optionID))
|
||||
copy(buf[16:], nonce)
|
||||
return sha256.Sum256(buf)
|
||||
}
|
||||
@@ -1,30 +1,56 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"time"
|
||||
"database/sql"
|
||||
"encoding/pem"
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type BlindSignRequest struct {
|
||||
type BlindSign struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
IssueID int64 `json:"issue_id"`
|
||||
Created time.Time `json:"created"`
|
||||
}
|
||||
|
||||
type BlindSignRequestModel struct {
|
||||
type BlindSignModel struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
func (m BlindSignRequestModel) Insert(blind_sign *BlindSignRequest) error {
|
||||
func (m BlindSignModel) Get(userID int64, issueID int64) (*BlindSign, error) {
|
||||
query :=
|
||||
`SELECT user_id, issue_id, created
|
||||
FROM blind_signs
|
||||
WHERE user_id = $1 AND issue_id = $2`
|
||||
|
||||
args := []interface{}{
|
||||
userID,
|
||||
issueID,
|
||||
}
|
||||
|
||||
var blindSign BlindSign
|
||||
|
||||
err := m.DB.QueryRow(query, args...).Scan(&blindSign.UserID, &blindSign.IssueID, &blindSign.Created)
|
||||
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, sql.ErrNoRows):
|
||||
return nil, ErrRecordNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &blindSign, nil
|
||||
}
|
||||
|
||||
func (m BlindSignModel) Insert(blind_sign *BlindSign) error {
|
||||
query := `
|
||||
INSERT INTO blind_sign_requests (user_id, issue_id)
|
||||
INSERT INTO blind_signs (user_id, issue_id)
|
||||
VALUES ($1, $2)
|
||||
RETURNING created`
|
||||
|
||||
@@ -33,12 +59,16 @@ RETURNING created`
|
||||
blind_sign.IssueID,
|
||||
}
|
||||
|
||||
return m.DB.QueryRow(query, args...).Scan(
|
||||
&blind_sign.Created,
|
||||
)
|
||||
err := m.DB.QueryRow(query, args...).Scan(&blind_sign.Created)
|
||||
if pgErr, ok := err.(*pq.Error); ok {
|
||||
if pgErr.Code == "23505" {
|
||||
return ErrDuplicateBlindSign
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m BlindSignRequestModel) BlindSign(issueID int64, blindedVoteBytes []byte) ([]byte, error) {
|
||||
func (m BlindSignModel) BlindSign(issueID int64, blindedVoteBytes []byte) ([]byte, error) {
|
||||
if issueID < 1 {
|
||||
return nil, ErrRecordNotFound
|
||||
}
|
||||
@@ -77,12 +107,3 @@ func (m BlindSignRequestModel) BlindSign(issueID int64, blindedVoteBytes []byte)
|
||||
|
||||
return sig.Bytes(), nil
|
||||
}
|
||||
|
||||
func parsePrivateKey(pemBytes []byte) (*rsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to decode PEM block")
|
||||
}
|
||||
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package data
|
||||
|
||||
import(
|
||||
"crypto/rsa"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"crypto/x509"
|
||||
)
|
||||
|
||||
func parsePrivateKey(pemBytes []byte) (*rsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to decode PEM block")
|
||||
}
|
||||
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
}
|
||||
+57
-28
@@ -2,11 +2,12 @@ package data
|
||||
|
||||
import (
|
||||
"time"
|
||||
"party.at/party/internal/validator"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"party.at/party/internal/validator"
|
||||
)
|
||||
|
||||
type Issue struct {
|
||||
@@ -38,8 +39,8 @@ type IssueModel struct {
|
||||
}
|
||||
|
||||
func (m IssueModel) Insert(issue *Issue) error {
|
||||
query := `
|
||||
INSERT INTO issues (title, description, start_time, end_time, rsa_n, rsa_e, rsa_private_pem)
|
||||
query :=
|
||||
`INSERT INTO issues (title, description, start_time, end_time, rsa_n, rsa_e, rsa_private_pem)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id, created, version`
|
||||
|
||||
@@ -60,14 +61,64 @@ RETURNING id, created, version`
|
||||
)
|
||||
}
|
||||
|
||||
// Add a placeholder method for fetching a specific record from the issues table.
|
||||
func (m IssueModel) InsertWithOptions(issue *Issue, options []*Option) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
tx, err := m.DB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
query :=
|
||||
`INSERT INTO issues (title, description, start_time, end_time, rsa_n, rsa_e, rsa_private_pem)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id, created, version`
|
||||
|
||||
args := []any{
|
||||
issue.Title,
|
||||
issue.Description,
|
||||
issue.StartTime,
|
||||
issue.EndTime,
|
||||
issue.N,
|
||||
issue.E,
|
||||
issue.PrivatePem,
|
||||
}
|
||||
|
||||
err = tx.QueryRowContext(ctx, query, args...).Scan(&issue.ID, &issue.Created, &issue.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option.IssueID = issue.ID
|
||||
err = tx.QueryRowContext(ctx,
|
||||
`INSERT INTO options (issue_id, label)
|
||||
VALUES ($1, $2)
|
||||
RETURNING id, created, version`,
|
||||
option.IssueID,
|
||||
option.Label,
|
||||
).Scan(&option.ID, &option.Created, &option.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m IssueModel) Get(id int64) (*Issue, error) {
|
||||
if id < 1 {
|
||||
return nil, ErrRecordNotFound
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT id, title, description, start_time, end_time, rsa_n, rsa_e, rsa_private_pem, created, version
|
||||
query :=
|
||||
`SELECT id, title, description, start_time, end_time, rsa_n, rsa_e, rsa_private_pem, created, version
|
||||
FROM issues
|
||||
WHERE id = $1`
|
||||
|
||||
@@ -138,35 +189,25 @@ func (m IssueModel) Update(issue *Issue) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add a placeholder method for deleting a specific record from the issues table.
|
||||
func (m IssueModel) Delete(id int64) error {
|
||||
if id < 1 {
|
||||
return ErrRecordNotFound
|
||||
}
|
||||
|
||||
// Construct the SQL query to delete the record.
|
||||
query := `
|
||||
DELETE FROM issues
|
||||
WHERE id = $1`
|
||||
|
||||
// Execute the SQL query using the Exec() method, passing in the id variable as
|
||||
// the value for the placeholder parameter. The Exec() method returns a sql.Result
|
||||
// object.
|
||||
result, err := m.DB.Exec(query, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Call the RowsAffected() method on the sql.Result object to get the number of rows
|
||||
// affected by the query.
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If no rows were affected, we know that the issues table didn't contain a record
|
||||
// with the provided ID at the moment we tried to delete it. In that case we
|
||||
// return an ErrRecordNotFound error.
|
||||
if rowsAffected == 0 {
|
||||
return ErrRecordNotFound
|
||||
}
|
||||
@@ -175,7 +216,6 @@ func (m IssueModel) Delete(id int64) error {
|
||||
}
|
||||
|
||||
func (m IssueModel) GetAll(title string, filters Filters) ([]*Issue, Metadata, error) {
|
||||
// Construct the SQL query to retrieve all issue records.
|
||||
query := fmt.Sprintf(`
|
||||
SELECT
|
||||
COUNT(*) OVER(), id, title, description, start_time, end_time, rsa_n, rsa_e, rsa_private_pem, created, version
|
||||
@@ -193,7 +233,6 @@ func (m IssueModel) GetAll(title string, filters Filters) ([]*Issue, Metadata, e
|
||||
filters.sortDirection(),
|
||||
)
|
||||
|
||||
// Create a context with a 3-second timeout.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -204,20 +243,14 @@ func (m IssueModel) GetAll(title string, filters Filters) ([]*Issue, Metadata, e
|
||||
return nil, Metadata{}, err
|
||||
}
|
||||
|
||||
// Importantly, defer a call to rows.Close() to ensure that the resultset is closed
|
||||
// before GetAll() returns.
|
||||
defer rows.Close()
|
||||
|
||||
totalRecords := 0
|
||||
issues := []*Issue{}
|
||||
|
||||
// Use rows.Next to iterate through the rows in the resultset.
|
||||
for rows.Next() {
|
||||
// Initialize an empty Issue struct to hold the data for an individual issue.
|
||||
var issue Issue
|
||||
|
||||
// Scan the values from the row into the Issue struct. Again, note that we're
|
||||
// using the pq.Array() adapter on the genres field here.
|
||||
err := rows.Scan(
|
||||
&totalRecords,
|
||||
&issue.ID,
|
||||
@@ -236,18 +269,14 @@ func (m IssueModel) GetAll(title string, filters Filters) ([]*Issue, Metadata, e
|
||||
return nil, Metadata{}, err
|
||||
}
|
||||
|
||||
// Add the Issue struct to the slice.
|
||||
issues = append(issues, &issue)
|
||||
}
|
||||
|
||||
// When the rows.Next() loop has finished, call rows.Err() to retrieve any error
|
||||
// that was encountered during the iteration.
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, Metadata{}, err
|
||||
}
|
||||
|
||||
metadata := calculateMetadata(totalRecords, filters.Page, filters.PageSize)
|
||||
|
||||
// If everything went OK, then return the slice of issues.
|
||||
return issues, metadata, nil
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
var (
|
||||
ErrRecordNotFound = errors.New("record not found")
|
||||
ErrEditConflict = errors.New("edit conflict")
|
||||
ErrInvalidBlindedVote = errors.New("invalid blinded vote")
|
||||
)
|
||||
|
||||
type Models struct {
|
||||
@@ -17,7 +16,10 @@ type Models struct {
|
||||
Issues IssueModel
|
||||
Tokens TokenModel
|
||||
Permissions PermissionModel
|
||||
BlindSignRequests BlindSignRequestModel
|
||||
Roles RoleModel
|
||||
BlindSigns BlindSignModel
|
||||
Votes VoteModel
|
||||
Options OptionModel
|
||||
}
|
||||
|
||||
func NewModels(db *sql.DB) Models {
|
||||
@@ -27,6 +29,9 @@ func NewModels(db *sql.DB) Models {
|
||||
Issues: IssueModel{DB: db},
|
||||
Tokens: TokenModel{DB: db},
|
||||
Permissions: PermissionModel{DB: db},
|
||||
BlindSignRequests: BlindSignRequestModel{DB: db},
|
||||
Roles: RoleModel{DB: db},
|
||||
BlindSigns: BlindSignModel{DB: db},
|
||||
Votes: VoteModel{DB: db},
|
||||
Options: OptionModel{DB: db},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
"database/sql"
|
||||
|
||||
"party.at/party/internal/validator"
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
ID int64 `json:"id"`
|
||||
IssueID int64 `json:"issue_id"`
|
||||
Label string `json:"label"`
|
||||
Created time.Time `json:"created"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
func ValidateOption(v *validator.Validator, option *Option) {
|
||||
v.Check(option.Label != "", "options", "option labels must not be empty")
|
||||
}
|
||||
|
||||
type OptionModel struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
func (m OptionModel) Insert(option *Option) error {
|
||||
query := `
|
||||
INSERT INTO options (issue_id, label)
|
||||
VALUES ($1, $2)
|
||||
RETURNING id, created, version`
|
||||
|
||||
args := []interface{}{
|
||||
option.IssueID,
|
||||
option.Label,
|
||||
}
|
||||
|
||||
return m.DB.QueryRow(query, args...).Scan(
|
||||
&option.ID,
|
||||
&option.Created,
|
||||
&option.Version,
|
||||
)
|
||||
}
|
||||
|
||||
func (m OptionModel) GetAllForIssue(issueID int64) ([]*Option, error) {
|
||||
query := `
|
||||
SELECT id, issue_id, label, created, version
|
||||
FROM options
|
||||
WHERE issue_id = $1
|
||||
ORDER BY id`
|
||||
|
||||
rows, err := m.DB.Query(query, issueID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var options []*Option
|
||||
for rows.Next() {
|
||||
var option Option
|
||||
err := rows.Scan(&option.ID, &option.IssueID, &option.Label, &option.Created, &option.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options = append(options, &option)
|
||||
}
|
||||
return options, rows.Err()
|
||||
}
|
||||
|
||||
func (m OptionModel) Get(id int64) (*Option, error) {
|
||||
if id < 1 {
|
||||
return nil, ErrRecordNotFound
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT id, issue_id, label, created, version
|
||||
FROM options
|
||||
WHERE id = $1`
|
||||
|
||||
var option Option
|
||||
|
||||
err := m.DB.QueryRow(query, id).Scan(
|
||||
&option.ID,
|
||||
&option.IssueID,
|
||||
&option.Label,
|
||||
&option.Created,
|
||||
&option.Version,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, sql.ErrNoRows):
|
||||
return nil, ErrRecordNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &option, nil
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type Permissions []string
|
||||
@@ -24,14 +23,15 @@ type PermissionModel struct {
|
||||
}
|
||||
|
||||
func (m PermissionModel) GetAllForUser(userID int64) (Permissions, error) {
|
||||
query :=`
|
||||
query := `
|
||||
SELECT permissions.code
|
||||
FROM permissions
|
||||
INNER JOIN users_permissions ON users_permissions.permission_id = permissions.id
|
||||
INNER JOIN users ON users_permissions.user_id = users.id
|
||||
WHERE users.id = $1`
|
||||
INNER JOIN roles_permissions ON roles_permissions.permission_id = permissions.id
|
||||
INNER JOIN roles ON roles_permissions.role_id = roles.id
|
||||
INNER JOIN users_roles ON users_roles.role_id = roles.id
|
||||
WHERE users_roles.user_id = $1`
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
rows, err := m.DB.QueryContext(ctx, query, userID)
|
||||
@@ -43,27 +43,10 @@ WHERE users.id = $1`
|
||||
var permissions Permissions
|
||||
for rows.Next() {
|
||||
var permission string
|
||||
err := rows.Scan(&permission)
|
||||
if err != nil {
|
||||
if err := rows.Scan(&permission); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
permissions = append(permissions, permission)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
func (m PermissionModel) AddForUser(userID int64, codes ...string) error {
|
||||
query :=`
|
||||
INSERT INTO users_permissions
|
||||
SELECT $1, permissions.id FROM permissions WHERE permissions.code = ANY($2)`
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
|
||||
defer cancel()
|
||||
_, err := m.DB.ExecContext(ctx, query, userID, pq.Array(codes))
|
||||
return err
|
||||
return permissions, rows.Err()
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
RoleViewer = "viewer"
|
||||
RoleContributor = "contributor"
|
||||
RoleAdmin = "admin"
|
||||
RoleMemberOfParliament = "member_of_parliament"
|
||||
RolePartyLeadership = "party_leadership"
|
||||
)
|
||||
|
||||
type Role struct {
|
||||
ID int64
|
||||
Code string
|
||||
}
|
||||
|
||||
type RoleModel struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
func (m RoleModel) AssignToUser(userID int64, roleCode string) error {
|
||||
query := `
|
||||
INSERT INTO users_roles
|
||||
SELECT $1, id FROM roles WHERE code = $2
|
||||
ON CONFLICT DO NOTHING`
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := m.DB.ExecContext(ctx, query, userID, roleCode)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m RoleModel) GetAllForUser(userID int64) ([]Role, error) {
|
||||
query := `
|
||||
SELECT roles.id, roles.code
|
||||
FROM roles
|
||||
INNER JOIN users_roles ON users_roles.role_id = roles.id
|
||||
WHERE users_roles.user_id = $1`
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
rows, err := m.DB.QueryContext(ctx, query, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var roles []Role
|
||||
for rows.Next() {
|
||||
var role Role
|
||||
if err := rows.Scan(&role.ID, &role.Code); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
roles = append(roles, role)
|
||||
}
|
||||
return roles, rows.Err()
|
||||
}
|
||||
+10
-1
@@ -33,7 +33,6 @@ func generateToken(userID int64, userIdentityID int64, ttl time.Duration, scope
|
||||
Scope: scope,
|
||||
}
|
||||
|
||||
// Initialize a zero-valued byte slice with a length of 16 bytes.
|
||||
randomBytes := make([]byte, 16)
|
||||
|
||||
_, err := rand.Read(randomBytes)
|
||||
@@ -82,6 +81,16 @@ func (m TokenModel) Insert(token *Token) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (m TokenModel) Delete(hash []byte) error {
|
||||
query := `DELETE FROM tokens WHERE hash = $1`
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := m.DB.ExecContext(ctx, query, hash)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m TokenModel) DeleteAllForUser(scope string, userID int64) error {
|
||||
query :=`
|
||||
DELETE FROM tokens
|
||||
|
||||
@@ -117,13 +117,8 @@ SELECT id, provider_id, user_id, provider_user, password, version
|
||||
FROM user_identities
|
||||
WHERE id = $1`
|
||||
|
||||
// Declare a User struct to hold the data returned by the query.
|
||||
var userIdentity UserIdentity
|
||||
|
||||
// Execute the query using the QueryRow() method, passing in the provided id value
|
||||
// as a placeholder parameter, and scan the response data into the fields of the
|
||||
// User struct. Importantly, notice that we need to convert the scan target for the
|
||||
// genres column using the pq.Array() adapter function again.
|
||||
err := m.DB.QueryRow(query, id).Scan(
|
||||
&userIdentity.ID,
|
||||
&userIdentity.ProviderID,
|
||||
@@ -141,7 +136,7 @@ WHERE id = $1`
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Otherwise, return a pointer to the User struct.
|
||||
|
||||
return &userIdentity, nil
|
||||
}
|
||||
|
||||
@@ -156,10 +151,6 @@ FROM user_identities identity
|
||||
JOIN users u on identity.user_id = u.id
|
||||
WHERE u.id = $1`
|
||||
|
||||
// Execute the query using the QueryRow() method, passing in the provided id value
|
||||
// as a placeholder parameter, and scan the response data into the fields of the
|
||||
// User struct. Importantly, notice that we need to convert the scan target for the
|
||||
// genres column using the pq.Array() adapter function again.
|
||||
rows, err := m.DB.Query(query, user_id)
|
||||
if err != nil {
|
||||
switch {
|
||||
@@ -244,7 +235,6 @@ func (m UserIdentityModel) Update(user *UserIdentity) error {
|
||||
WHERE id = $3 AND version = $4
|
||||
RETURNING version`
|
||||
|
||||
// Create an args slice containing the values for the placeholder parameters.
|
||||
args := []interface{}{
|
||||
user.ProviderUserID,
|
||||
user.Password.hash,
|
||||
@@ -275,29 +265,20 @@ func (m UserIdentityModel) Delete(id int64) error {
|
||||
return ErrRecordNotFound
|
||||
}
|
||||
|
||||
// Construct the SQL query to delete the record.
|
||||
query := `
|
||||
DELETE FROM user_identities
|
||||
WHERE id = $1`
|
||||
|
||||
// Execute the SQL query using the Exec() method, passing in the id variable as
|
||||
// the value for the placeholder parameter. The Exec() method returns a sql.Result
|
||||
// object.
|
||||
result, err := m.DB.Exec(query, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Call the RowsAffected() method on the sql.Result object to get the number of rows
|
||||
// affected by the query.
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If no rows were affected, we know that the issues table didn't contain a record
|
||||
// with the provided ID at the moment we tried to delete it. In that case we
|
||||
// return an ErrRecordNotFound error.
|
||||
if rowsAffected == 0 {
|
||||
return ErrRecordNotFound
|
||||
}
|
||||
|
||||
+60
-9
@@ -2,17 +2,20 @@ package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"party.at/party/internal/validator"
|
||||
"database/sql"
|
||||
"github.com/lib/pq"
|
||||
"errors"
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"party.at/party/internal/validator"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDuplicateEmail = errors.New("duplicate email")
|
||||
ErrDuplicateUser = errors.New("duplicate username")
|
||||
ErrDuplicateEmail = errors.New("duplicate email")
|
||||
ErrDuplicateUser = errors.New("duplicate username")
|
||||
ErrInvalidCredentials = errors.New("invalid credentials")
|
||||
)
|
||||
|
||||
var AnonymousUser = &User{}
|
||||
@@ -128,8 +131,8 @@ func (m UserModel) Get(id int64) (*User, error) {
|
||||
}
|
||||
|
||||
// Define the SQL query for retrieving the issue data.
|
||||
query :=`
|
||||
SELECT id, email, phone_number, country, name, alt_name, date_of_birth, address, created, last_login, activated, version
|
||||
query :=
|
||||
`SELECT id, email, phone_number, country, name, alt_name, date_of_birth, address, created, last_login, activated, version
|
||||
FROM users
|
||||
WHERE id = $1`
|
||||
|
||||
@@ -307,6 +310,54 @@ func (m UserModel) Update(user *User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m UserModel) GetAll(filters Filters) ([]*User, Metadata, error) {
|
||||
query := fmt.Sprintf(`
|
||||
SELECT COUNT(*) OVER(), id, email, phone_number, country, name, alt_name, date_of_birth, address, created, last_login, activated, version
|
||||
FROM users
|
||||
ORDER BY %s %s, id ASC
|
||||
LIMIT $1 OFFSET $2`,
|
||||
filters.sortColumn(),
|
||||
filters.sortDirection(),
|
||||
)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
rows, err := m.DB.QueryContext(ctx, query, filters.limit(), filters.offset())
|
||||
if err != nil {
|
||||
return nil, Metadata{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var totalRecords int
|
||||
var users []*User
|
||||
for rows.Next() {
|
||||
var user User
|
||||
if err := rows.Scan(
|
||||
&totalRecords,
|
||||
&user.ID,
|
||||
&user.Email,
|
||||
&user.PhoneNumber,
|
||||
&user.Country,
|
||||
&user.Name,
|
||||
&user.AltName,
|
||||
&user.DateOfBirth,
|
||||
&user.Address,
|
||||
&user.Created,
|
||||
&user.LastLogin,
|
||||
&user.Activated,
|
||||
&user.Version,
|
||||
); err != nil {
|
||||
return nil, Metadata{}, err
|
||||
}
|
||||
users = append(users, &user)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, Metadata{}, err
|
||||
}
|
||||
return users, calculateMetadata(totalRecords, filters.Page, filters.PageSize), nil
|
||||
}
|
||||
|
||||
func (m UserModel) Delete(id int64) error {
|
||||
if id < 1 {
|
||||
return ErrRecordNotFound
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
"database/sql"
|
||||
"math/big"
|
||||
"context"
|
||||
|
||||
"party.at/party/internal/crypto"
|
||||
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDuplicateBlindSign = errors.New("user has already requested a blind signature for this issue")
|
||||
ErrInvalidBlindedVote = errors.New("blinded_vote is out of valid range [1, n-1]")
|
||||
ErrInvalidSignature = errors.New("signature verification failed")
|
||||
ErrDuplicateVote = errors.New("this signature has already been used to cast a vote")
|
||||
)
|
||||
|
||||
|
||||
type Vote struct {
|
||||
ID int64 `json:"id"`
|
||||
OptionID int64 `json:"option_id"`
|
||||
Nonce []byte `json:"nonce"`
|
||||
Signature []byte `json:"signature"`
|
||||
Created time.Time `json:"created"`
|
||||
}
|
||||
|
||||
type VoteModel struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
|
||||
func (m VoteModel) CountForOption(optionID int64) (int64, error) {
|
||||
query := `
|
||||
SELECT COUNT(v.id)
|
||||
FROM options o
|
||||
LEFT JOIN votes v ON v.option_id = o.id
|
||||
WHERE o.id = $1
|
||||
GROUP BY o.id`
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
|
||||
defer cancel()
|
||||
|
||||
var count int64
|
||||
|
||||
err := m.DB.QueryRowContext(ctx, query, optionID).Scan(&count)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (m VoteModel) Insert(vote *Vote, issue *Issue) error {
|
||||
|
||||
n := new(big.Int).SetBytes(issue.N)
|
||||
e := issue.E
|
||||
|
||||
// Recompute m = SHA-256(issueID || optionID || nonce)
|
||||
expected := crypto.VoteMessage(issue.ID, vote.OptionID, vote.Nonce)
|
||||
|
||||
// Verify: s^e mod n == m
|
||||
sig := new(big.Int).SetBytes(vote.Signature)
|
||||
eInt := big.NewInt(int64(e))
|
||||
recovered := new(big.Int).Exp(sig, eInt, n)
|
||||
expectedInt := new(big.Int).SetBytes(expected[:])
|
||||
|
||||
if recovered.Cmp(expectedInt) != 0 {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
|
||||
query :=
|
||||
`INSERT INTO votes (option_id, nonce, signature)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, created`
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
args := []interface{}{
|
||||
vote.OptionID,
|
||||
vote.Nonce,
|
||||
vote.Signature,
|
||||
}
|
||||
|
||||
err := m.DB.QueryRowContext(ctx, query, args...).Scan(&vote.ID, &vote.Created)
|
||||
if pgErr, ok := err.(*pq.Error); ok {
|
||||
if pgErr.Code == "23505" {
|
||||
if pgErr.Constraint == "votes_signature_key" {
|
||||
return ErrDuplicateVote
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user