From 4b5947537b3a985c7e0778255099c6ab4f36ac21 Mon Sep 17 00:00:00 2001 From: EDRipper Date: Wed, 21 Jan 2026 15:18:03 -0500 Subject: [PATCH 1/2] trying to properly model models --- backend/api/auth.rb | 9 +--- backend/api/designs.rb | 79 +++++++------------------------- backend/api/session_helpers.rb | 3 +- backend/api/shop.rb | 25 ++-------- backend/api/stickers.rb | 32 +++---------- backend/models/design.rb | 71 ++++++++++++++++++++++++++++ backend/models/shop_record.rb | 22 +++++++++ backend/models/sticker_record.rb | 36 +++++++++++++++ backend/models/user.rb | 29 ++++++++++++ docker-compose.yml | 6 +-- 10 files changed, 192 insertions(+), 120 deletions(-) create mode 100644 backend/models/design.rb create mode 100644 backend/models/shop_record.rb create mode 100644 backend/models/sticker_record.rb create mode 100644 backend/models/user.rb diff --git a/backend/api/auth.rb b/backend/api/auth.rb index 7c96878..3d8f5d2 100644 --- a/backend/api/auth.rb +++ b/backend/api/auth.rb @@ -11,12 +11,7 @@ class Auth < Grape::API get 'oidc/callback' do auth = env['omniauth.auth'] - session[:user] = { - id: auth.uid, - email: auth.info.email, - name: auth.info.name, - slack_id: auth.info.slack_id - } + session[:user] = User.from_omniauth(auth).to_h redirect ENV.fetch('AUTH_SUCCESS_REDIRECT', '/') end @@ -27,7 +22,7 @@ class Auth < Grape::API get :me do error!('Unauthorized', 401) unless current_user - current_user + current_user.to_h end end end diff --git a/backend/api/designs.rb b/backend/api/designs.rb index b07d0ee..b3f380f 100644 --- a/backend/api/designs.rb +++ b/backend/api/designs.rb @@ -1,81 +1,34 @@ # frozen_string_literal: true -class StickersTable < AirctiveRecord::Base - self.base_key = ENV['AIRTABLE_BASE_ID'] - self.table_name = "stickerDB" -end - -class ShopTable < AirctiveRecord::Base - self.base_key = ENV['AIRTABLE_BASE_ID'] - self.table_name = "shop" -end - -class DesignsTable < AirctiveRecord::Base - self.base_key = ENV['AIRTABLE_BASE_ID'] - self.table_name = "designs" -end - -class Designs < Base - DESIGN_ALLOWED_FIELDS = %w[Name Description Image_URL CDN_URL].freeze - +class Designs < Grape::API + format :json + helpers SessionHelpers resource :designs do get :all do error!('Unauthorized', 401) unless current_user - user_id = current_user[:slack_id] || current_user[:id] - records = DesignsTable.all - approved = records.select { |r| r['Status']&.downcase == 'approved' } - approved.map do |record| - voted_by = (record['voted_by'] || '').split(',').map(&:strip) - { - id: record.id, - cdn_url: record['CDN_URL'], - name: record['Name'], - votes: record['Votes'] || 0, - voted: voted_by.include?(user_id) - } - end + user_id = current_user.identifier + DesignsTable.all.map { |d| d.as_approved_json(user_id) } end get do error!('Unauthorized', 401) unless current_user - slack_id = current_user[:slack_id] || current_user[:id] - records = DesignsTable.all - records.select { |r| r['Slack_ID'] == slack_id }.map do |record| - { - id: record.id, - cdn_url: record['CDN_URL'], - slack_id: record['Slack_ID'], - name: record['Name'], - status: record['Status'], - votes: record['Votes'], - submitted_at: record['Created'] - } - end + user_id = current_user.identifier + Design.by_user(user_id).all.map { |d| d.as_approved_json(user_id) } end post do error!('Unauthorized', 401) unless current_user - puts "=== DEBUG: POST /designs ===" - puts "params: #{params.inspect}" - puts "params[:fields]: #{params[:fields].inspect}" - fields = (params[:fields] || {}).transform_keys(&:to_s) - puts "fields after transform: #{fields.inspect}" - safe_fields = fields.slice(*DESIGN_ALLOWED_FIELDS) - puts "safe_fields after slice: #{safe_fields.inspect}" - safe_fields['Slack_ID'] = current_user[:slack_id] || current_user[:id] - safe_fields['Status'] = 'Pending' + safe_fields = (params[:fields] || {}).slice(*Design::ALLOWED_FIELDS) + safe_fields['slack_id'] = current_user.identifier safe_fields['Votes'] = 0 - puts "final safe_fields: #{safe_fields.inspect}" - begin - result = DesignsTable.create(safe_fields) - puts "create result: #{result.inspect}" - result - rescue => e - puts "ERROR: #{e.class}: #{e.message}" - puts e.backtrace.first(10).join("\n") - raise - end + safe_fields['Status'] = 'pending' + Design.create(safe_fields) end + + + + + route_param :id do post :vote do diff --git a/backend/api/session_helpers.rb b/backend/api/session_helpers.rb index 7022980..62db7f4 100644 --- a/backend/api/session_helpers.rb +++ b/backend/api/session_helpers.rb @@ -6,7 +6,8 @@ module SessionHelpers end def current_user - session[:user] + return nil unless session[:user] + @current_user ||= User.new(session[:user]) end def authenticate! diff --git a/backend/api/shop.rb b/backend/api/shop.rb index afe92bc..dec7542 100644 --- a/backend/api/shop.rb +++ b/backend/api/shop.rb @@ -1,34 +1,19 @@ # frozen_string_literal: true -class Shop < Base +class Shop < Grape::API + format :json helpers SessionHelpers resource :shop do get do - records = ShopTable.all - records.map do |record| - { - id: record.id, - name: record["Name"], - image: record["CDN_URL"], - price: record["Cost"], - description: record["Description"] - } - end + ShopRecord.all.map(&:as_json) end route_param :id, type: String do get do - record = ShopTable.find(params[:id]) + record = shopRecord.find(params[:id]) error!('not found', 404) unless record - { - id: record.id, - name: record["Name"], - image: record["CDN_URL"], - price: record["cost"], - description: record["Description"] - } - end + record.as_json end end end diff --git a/backend/api/stickers.rb b/backend/api/stickers.rb index 0bfeeec..15bc78f 100644 --- a/backend/api/stickers.rb +++ b/backend/api/stickers.rb @@ -1,41 +1,21 @@ # frozen_string_literal: true -class Stickers < Base +class Stickers < Grape::API + format :json helpers SessionHelpers resource :stickers do get do - user_id = current_user ? (current_user[:slack_id] || current_user[:id]) : nil - records = StickersTable.all - records.map do |record| - owned_by = record["owned_by"] || "" - owners = owned_by.split(',').map(&:strip) - { - id: record.id, - name: record["Sticker Name"], - image: record["CDN_URL"], - artist: record["Artist"], - event: record["Event"], - event_URL: record["event_URL"], - owned_by: owned_by, - owned: user_id && owners.include?(user_id) - } - end + user_id = current_user&.identifier + StickerRecord.all.map { |r| r.as_json(user_id: user_id) } end route_param :id, type: String do before { authenticate! } get do - record = StickersTable.find(params[:id]) + record = StickersRecord.find(params[:id]) error!('not found', 404) unless record - { - id: record.id, - name: record["Sticker Name"], - image: record["CDN_URL"], - artist: record["Artist"], - event: record["Event"], - event_URL: record["event_URL"] - } + record.as_detail_json end end end diff --git a/backend/models/design.rb b/backend/models/design.rb new file mode 100644 index 0000000..f4ff4e6 --- /dev/null +++ b/backend/models/design.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +class Design < AirctiveRecord::Base + self.base_key = ENV['AIRTABLE_BASE_ID'] + self.table_name = 'designs' + + field :name, 'Name' + field :description, 'Description' + field :image_url, 'Image_URL' + field :cdn_url, 'CDN_URL' + field :slack_id, 'slack_id' + field :status, 'Status' + field :votes, 'Votes' + field :voted_by, 'voted_by' + field :created, 'Created' + + ALLOWED_FIELDS = %w[Name Description Image_URL].freeze + + scope :approved, -> { where(status: 'approved') } + scope :by_user, ->(slack_id) { where(slack_id: slack_id) } + + def vote!(user_id) + voted_users = voted_list + + if voted_users.include?(user_id) + voted_users.delete(user_id) + self.votes = [votes.to_i - 1, 0].max + else + voted_users << user_id + self.votes = votes.to_i + 1 + end + + self.voted_by = voted_users.join(',') + save + end + + def voted_by_user?(user_id) + voted_list.include?(user_id) + end + + def as_json(options = nil) + user_id = options&.dig(:user_id) + { + id: id, + cdn_url: cdn_url, + slack_id: slack_id, + name: name, + status: status, + votes: votes || 0, + submitted_at: created, + voted: user_id ? voted_by_user?(user_id) : false + } + end + + def as_approved_json(options = nil) + user_id = options&.dig(:user_id) + { + id: id, + cdn_url: cdn_url, + name: name, + votes: votes || 0, + voted: user_id ? voted_by_user?(user_id) : false + } + end + + private + + def voted_list + (voted_by || '').split(',').map(&:strip).reject(&:empty?) + end +end \ No newline at end of file diff --git a/backend/models/shop_record.rb b/backend/models/shop_record.rb new file mode 100644 index 0000000..0703d1b --- /dev/null +++ b/backend/models/shop_record.rb @@ -0,0 +1,22 @@ + +# frozen_string_literal: true + +class ShopRecord < AirctiveRecord::Base + self.base_key = ENV['AIRTABLE_BASE_ID'] + self.table_name = 'shop' + + field :name, 'Name' + field :image, 'CDN_URL' + field :price, 'Cost' + field :description, 'Description' + + def as_json(options = nil) + { + id: id, + name: name, + image: image, + price: price, + description: description + } + end +end \ No newline at end of file diff --git a/backend/models/sticker_record.rb b/backend/models/sticker_record.rb new file mode 100644 index 0000000..547d5c6 --- /dev/null +++ b/backend/models/sticker_record.rb @@ -0,0 +1,36 @@ +frozen_string_literal: true + +class StickerRecord < AirctiveRecord::Base + self.base_key = ENV['AIRTABLE_BASE_ID'] + self.table_name = 'stickerDB' + + field :name, 'Sticker Name' + field :image, 'CDN_URL' + field :artist, 'Artist' + field :event, 'Event' + field :owned_by, 'owned_by' + + def as_json(options = nil) + user_id = options&.dig(:user_id) + owners = (owned_by || '').split(',').map(&:strip) + { + id: id, + name: name, + image: image, + artist: artist, + event: event, + owned_by: owned_by, + owned: user_id && owners.include?(user_id) + } + end + + def as_detail_json(options = nil) + { + id: id, + name: name, + image: image, + artist: artist, + event: event + } + end +end \ No newline at end of file diff --git a/backend/models/user.rb b/backend/models/user.rb new file mode 100644 index 0000000..aa3fd22 --- /dev/null +++ b/backend/models/user.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class User + attr_accessor :id, :email, :name, :slack_id + + def initialize(attrs = {}) + @id = attrs[:id] + @email = attrs[:email] + @name = attrs[:name] + @slack_id = attrs[:slack_id] + end + + def self.from_omniauth(auth) + new( + id: auth.uid, + email: auth.info.email, + name: auth.info.name, + slack_id: auth.info.slack_id + ) + end + + def identifier + slack_id || id + end + + def to_h + { id: id, email: email, name: name, slack_id: slack_id } + end +end \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index da8e989..86ea51b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ services: - path: ./backend/.env required: false environment: - FRONTEND_URL: ${FRONTEND_URL:-http://localhost:5173} + FRONTEND_URL: ${FRONTEND_URL:-https://stickers.hackclub.com} frontend: build: @@ -20,7 +20,7 @@ services: expose: - "3000" environment: - ORIGIN: ${ORIGIN:-http://localhost:5173} + ORIGIN: ${ORIGIN:-https://stickers.hackclub.com} BACKEND_URL: http://backend:9292 - depends_on: + depends_on: - backend From 8a93c3f6dd1b3034f63c19792fa81e8941f4384c Mon Sep 17 00:00:00 2001 From: EDRipper Date: Wed, 21 Jan 2026 15:18:39 -0500 Subject: [PATCH 2/2] sure whatever --- Dockerfile | 7 ++++++- backend/.dockerignore | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 397aad7..1acaf90 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,7 @@ +# ============================================================================= +# BACKEND (Ruby/Puma) +# ============================================================================= FROM ruby:3.3-alpine AS backend RUN apk add --no-cache build-base tzdata WORKDIR /app @@ -10,7 +13,9 @@ COPY backend/ . EXPOSE 9292 CMD ["bundle", "exec", "puma", "-p", "9292", "-e", "production"] -# frontend +# ============================================================================= +# FRONTEND (SvelteKit/Node) +# ============================================================================= FROM node:22-alpine AS frontend WORKDIR /app diff --git a/backend/.dockerignore b/backend/.dockerignore index 9638772..11b431c 100644 --- a/backend/.dockerignore +++ b/backend/.dockerignore @@ -1,7 +1,8 @@ .bundle -vendor/bundle .env .env.* !.env.example .git *.log +.vendor +