mirror of
https://github.com/System-End/stickers.git
synced 2026-04-19 22:05:10 +00:00
Merge pull request #2 from hackclub/coolify-fix
really meant to do that on main
This commit is contained in:
commit
35e0809ce7
12 changed files with 200 additions and 122 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
.bundle
|
||||
vendor/bundle
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.git
|
||||
*.log
|
||||
.vendor
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
71
backend/models/design.rb
Normal file
71
backend/models/design.rb
Normal file
|
|
@ -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
|
||||
22
backend/models/shop_record.rb
Normal file
22
backend/models/shop_record.rb
Normal file
|
|
@ -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
|
||||
36
backend/models/sticker_record.rb
Normal file
36
backend/models/sticker_record.rb
Normal file
|
|
@ -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
|
||||
29
backend/models/user.rb
Normal file
29
backend/models/user.rb
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue