Skip to content

Commit

Permalink
make clubs and orgs different
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanfaerman committed Feb 7, 2024
1 parent 37de219 commit cf409d7
Show file tree
Hide file tree
Showing 13 changed files with 541 additions and 108 deletions.
64 changes: 51 additions & 13 deletions internal/handlers/memberships.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package handlers

import (
"errors"
"fmt"
"net/http"

"github.com/davecgh/go-spew/spew"
Expand Down Expand Up @@ -56,10 +58,48 @@ func (h Membership) Create(w http.ResponseWriter, r *http.Request) {
return
}
spew.Dump(group)
spew.Dump(r.Form.Get("callsign"))

if err := services.Membership.Create(ctx, services.Session.GetAccount(ctx), &group); err != nil {
if err := services.Membership.Create(ctx, services.Session.GetAccount(ctx), &group, r.Form.Get("callsign")); err != nil {
// TODO: handle this more gracefully
ErrorHandler(err)(w, r)
spew.Dump(err)
viewInput := views.MembershipCreateFormInput{
Name: group.Name,
Slug: group.Slug,
Callsign: r.Form.Get("callsign"),
}
viewErr := views.MembershipCreateFormError{}
switch {
case errors.Is(err, services.ErrAccountSetupInvalidCallsign):
viewErr.Callsign = "Invalid callsign"
case errors.Is(err, services.ErrClubRequiresCallsign):
viewErr.Callsign = "Clubs require a callsign"
case errors.Is(err, services.ErrAccountSetupCallsignTaken):
viewErr.Callsign = "Callsign already in use"
case errors.Is(err, services.ErrAccountSetupCallsignIndividual):
viewErr.Callsign = "Callsign must be for a club, not an individual"
case errors.Is(err, services.ErrCallsignCreationFailed):
viewErr.Callsign = "Unable to save callsign, try again"
default:
if errs, ok := err.(services.ValidationError); ok {
for field, e := range errs {
switch field {
case "Account.Name":
viewErr.Name = e
case "Account.Slug":
viewErr.Slug = e
}
}
} else {
ErrorHandler(err)(w, r)
return
}
}

v := views.Membership{
Kind: kind,
}
v.CreateFormWithError(viewInput, viewErr).Render(ctx, w)
return
}

Expand All @@ -69,33 +109,31 @@ func (h Membership) Create(w http.ResponseWriter, r *http.Request) {
case models.AccountKindOrganization:
w.Header().Set("HX-Redirect", named.URLFor("settings", "organizations"))
}
return

v := views.Membership{
Kind: kind,
}
v.Create().Render(ctx, w)
}

func (h Membership) CheckSlug(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
kind := models.ParseAccountKind(chi.URLParam(r, "kind"))
name := r.Form.Get("name")
spew.Dump(r.Form)

v := views.Membership{
Kind: kind,
}
slug := r.Form.Get("slug")
if slug == "" {

var slug string
if r.URL.Query().Get("source") != "slug" {
fmt.Println("WAT")
slug = services.Slugger.Generate(r.Context(), name)
} else {
fmt.Println("GONZO")
slug = r.Form.Get("slug")
}
var err error
slug, err = services.Slugger.ValidateUniqueForAccount(r.Context(), slug)
if err != nil {
v.SlugField(slug, "That organization ID is already in use").Render(r.Context(), w)
v.SlugField(views.MembershipCreateFormInput{Slug: slug}, views.MembershipCreateFormError{Slug: "That organization ID is already in use"}).Render(r.Context(), w)
return
}

v.SlugField(slug, "").Render(r.Context(), w)
v.SlugField(views.MembershipCreateFormInput{Slug: slug}, views.MembershipCreateFormError{}).Render(r.Context(), w)
}
2 changes: 1 addition & 1 deletion internal/handlers/static/styles/main.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion internal/handlers/static/styles/main.min.css

Large diffs are not rendered by default.

Binary file modified internal/handlers/static/styles/main.min.css.br
Binary file not shown.
Binary file modified internal/handlers/static/styles/main.min.css.gz
Binary file not shown.
97 changes: 95 additions & 2 deletions internal/services/memberships.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package services

import (
"context"
"database/sql"
"errors"
"fmt"

"github.com/davecgh/go-spew/spew"
"github.com/ryanfaerman/netctl/hamdb"
"github.com/ryanfaerman/netctl/internal/dao"
"github.com/ryanfaerman/netctl/internal/models"
)
Expand All @@ -13,7 +16,12 @@ type membership struct{}

var Membership membership

func (s membership) Create(ctx context.Context, owner, m *models.Account) error {
var (
ErrClubRequiresCallsign = errors.New("clubs require a callsign")
ErrCallsignCreationFailed = errors.New("unable to create callsign")
)

func (s membership) Create(ctx context.Context, owner, m *models.Account, callsigns ...string) error {
if owner.IsAnonymous() {
return fmt.Errorf("anonymous users cannot create organizations")
}
Expand All @@ -29,6 +37,76 @@ func (s membership) Create(ctx context.Context, owner, m *models.Account) error

qtx := global.dao.WithTx(tx)

var (
callsign string
callsignID int64
)
if m.Kind == models.AccountKindClub {
if len(callsigns) == 0 {
return ErrClubRequiresCallsign
}
if len(callsigns) > 0 {
callsign = callsigns[0]
if callsign == "" {
return ErrClubRequiresCallsign
}
}
}

if callsign != "" {
global.log.Debug("checking if callsign is already associated", "callsign", callsign)
_, err := qtx.FindAccountByCallsign(ctx, callsign)
if err != nil {
if err != sql.ErrNoRows {
return err
}
} else {
return ErrAccountSetupCallsignTaken
}

global.log.Debug("validating callsign with hamdb", "callsign", callsign)
license, err := hamdb.Lookup(ctx, callsign)
if err != nil {
return ErrAccountSetupInvalidCallsign
}
if license.Class != hamdb.ClubClass {
return ErrAccountSetupCallsignIndividual
}

global.log.Debug("creating callsign record", "callsign", callsign)
id, err := qtx.CreateCallsignAndReturnId(ctx, dao.CreateCallsignAndReturnIdParams{
Callsign: license.Call,
Class: int64(license.Class),
Expires: sql.NullTime{
Time: license.Expires.Value,
Valid: license.Expires.Known,
},
Status: int64(license.Status),
Latitude: sql.NullFloat64{
Float64: license.Lat.Value,
Valid: license.Lat.Known,
},
Longitude: sql.NullFloat64{
Float64: license.Lon.Value,
Valid: license.Lon.Known,
},
Firstname: sql.NullString{String: license.FirstName, Valid: true},
Middlename: sql.NullString{String: license.MiddleInitial, Valid: true},
Lastname: sql.NullString{String: license.LastName, Valid: true},
Suffix: sql.NullString{String: license.Suffix, Valid: true},
Address: sql.NullString{String: license.Address, Valid: true},
City: sql.NullString{String: license.City, Valid: true},
State: sql.NullString{String: license.State, Valid: true},
Zip: sql.NullString{String: license.Zip, Valid: true},
Country: sql.NullString{String: license.Country, Valid: true},
})
if err != nil {
return ErrCallsignCreationFailed
}
callsignID = id
m.Slug = license.Call
}

id, err := qtx.CreateAccount(ctx, dao.CreateAccountParams{
Name: m.Name,
Kind: int64(m.Kind),
Expand All @@ -40,6 +118,15 @@ func (s membership) Create(ctx context.Context, owner, m *models.Account) error
m.ID = id
spew.Dump("new account", m)

if callsignID != 0 {
if err := qtx.AssociateCallsignWithAccount(ctx, dao.AssociateCallsignWithAccountParams{
AccountID: m.ID,
CallsignID: callsignID,
}); err != nil {
return fmt.Errorf("error associating callsign with account: %w", err)
}
}

roleID, err := qtx.CreateRoleOnAccount(ctx, dao.CreateRoleOnAccountParams{
Name: "Owner",
AccountID: m.ID,
Expand All @@ -63,5 +150,11 @@ func (s membership) Create(ctx context.Context, owner, m *models.Account) error
return fmt.Errorf("error creating membership: %w", err)
}

return tx.Commit()
err = tx.Commit()
if err != nil {
return fmt.Errorf("error committing transaction: %w", err)
}
return nil
}

var ErrAccountSetupCallsignIndividual = fmt.Errorf("callsign is not a club")
105 changes: 105 additions & 0 deletions internal/sql/migrations/0013_deferred_foreign_keys.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
-- deferred_foreign_keys
-- +goose Up
-- +goose StatementBegin
CREATE TABLE temp_accounts_sessions (
account_id integer NOT NULL,
token text NOT NULL,

createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
createdBy text NOT NULL DEFAULT 'system',

PRIMARY KEY (account_id, token),

FOREIGN KEY (account_id) REFERENCES accounts(id) DEFERRABLE INITIALLY DEFERRED,
FOREIGN KEY (token) REFERENCES sessions(token) DEFERRABLE INITIALLY DEFERRED
);
INSERT INTO temp_accounts_sessions SELECT * FROM accounts_sessions;
DROP TABLE accounts_sessions;
ALTER TABLE temp_accounts_sessions RENAME TO accounts_sessions;


CREATE TABLE temp_emails (
id integer PRIMARY KEY,

createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
updatedAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,

account_id integer NOT NULL,

address text NOT NULL,
isPrimary boolean NOT NULL DEFAULT false,
isPublic boolean NOT NULL DEFAULT false,
isNotifiable boolean NOT NULL DEFAULT true,
verifiedAt timestamp,

FOREIGN KEY (account_id) REFERENCES accounts(id) DEFERRABLE INITIALLY DEFERRED,
UNIQUE (account_id, isPrimary),
UNIQUE (address)
);

INSERT INTO temp_emails SELECT * FROM emails;
DROP TABLE emails;
ALTER TABLE temp_emails RENAME TO emails;


CREATE TABLE temp_accounts_callsigns (
account_id integer NOT NULL,
callsign_id integer NOT NULL,

PRIMARY KEY (account_id, callsign_id),
UNIQUE (callsign_id),
FOREIGN KEY (account_id) REFERENCES accounts(id) DEFERRABLE INITIALLY DEFERRED,
FOREIGN KEY (callsign_id) REFERENCES callsigns(id) DEFERRABLE INITIALLY DEFERRED
);
INSERT INTO temp_accounts_callsigns SELECT * FROM accounts_callsigns;
DROP TABLE accounts_callsigns;
ALTER TABLE temp_accounts_callsigns RENAME TO accounts_callsigns;


CREATE TABLE temp_events_recovery (
id INTEGER PRIMARY KEY,
events_id INTEGER NOT NULL,
registered_fn TEXT NOT NULL DEFAULT '',
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

FOREIGN KEY(events_id) REFERENCES events(id) DEFERRABLE INITIALLY DEFERRED
);

INSERT INTO temp_events_recovery SELECT * FROM events_recovery;
DROP TABLE events_recovery;
ALTER TABLE temp_events_recovery RENAME TO events_recovery;


CREATE TABLE temp_net_sessions (
id INTEGER PRIMARY KEY,
net_id INTEGER NOT NULL,
stream_id TEXT NOT NULL,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

FOREIGN KEY(net_id) REFERENCES nets(id) DEFERRABLE INITIALLY DEFERRED
);
INSERT INTO temp_net_sessions SELECT * FROM net_sessions;
DROP TABLE net_sessions;
ALTER TABLE temp_net_sessions RENAME TO net_sessions;


CREATE TABLE temp_events (
id INTEGER PRIMARY KEY,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
stream_id TEXT NOT NULL,
account_id INTEGER NOT NULL,
event_type TEXT NOT NULL,
event_data BLOB NOT NULL,

FOREIGN KEY(account_id) REFERENCES accounts(id) DEFERRABLE INITIALLY DEFERRED
);
INSERT INTO temp_events SELECT * FROM events;
DROP TABLE events;
ALTER TABLE temp_events RENAME TO events;
-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin

-- +goose StatementEnd

10 changes: 6 additions & 4 deletions internal/views/account.templ
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,12 @@ templ (v Account) MembershipOverview(kind models.AccountKind) {
<div class="membership-list">
for _, m := range v.Memberships {
<div class="membership">
<a href="#" class="group">
{ m.Target.Name }
</a>
<span class="role badge">{ m.Role.Name }</span>
<div class="group">
<a href="#">
{ m.Target.Name }
</a>
<span class="role badge">{ m.Role.Name }</span>
</div>
<div class="actions">
<a href="#" class="button">Settings</a>
<a href="#" class="button danger">Leave</a>
Expand Down
Loading

0 comments on commit cf409d7

Please sign in to comment.